0%

实现倒角立方体

从平面到立体

先实现是个普通的立方体。
一个普通的立方体对大家来说想必难度不大,我们来实现一个倒角立方体。
前面我们生成里了一个三角形,这次我们来做一个倒角立方体。它有三个参数,

  • CubeSize (立方体的尺寸)

实现基础立方体

我们需要的参数只有一个 正方体的边长CubeSize

这个八个点我选择的是以原点(0,0,0)为中心的做法,使用HalfSize为每个点设置值。绘制的流程跟OpenGL类似,这里像是OpenGL的顶点数组。有图形学API的使用经验的,可以想想倒角的算法是怎样的。

1
2
3
4
5
6
7
8
9
10
11
12
13
const float HalfSize = CubeSize * 0.5f;

TArray<FVector> CorePoints;
CorePoints.Reserve(8);

CorePoints.Add(FVector(-HalfSize, -HalfSize, -HalfSize));
CorePoints.Add(FVector(HalfSize, -HalfSize, -HalfSize));
CorePoints.Add(FVector(-HalfSize, HalfSize, -HalfSize));
CorePoints.Add(FVector(HalfSize, HalfSize, -HalfSize));
CorePoints.Add(FVector(-HalfSize, -HalfSize, HalfSize));
CorePoints.Add(FVector(HalfSize, -HalfSize, HalfSize));
CorePoints.Add(FVector(-HalfSize, HalfSize, HalfSize));
CorePoints.Add(FVector(HalfSize, HalfSize, HalfSize));

八个面,用16个三角形既可。
生成面的方法是用两个三角形拼在一起组合为一个面。逆时针排序。

1
2
3
// v0, v1, v2, v3 是按逆时针排列的四个顶点
AddTriangle(V0, V1, V2);
AddTriangle(V0, V2, V3);

倒角

这个立方体有两种倒角,边缘倒角和角落倒角。
8个主面会向面的中心收缩,所以在就不能使用原始的点,要添加一个参数。
BevelSize :倒角的大小,不能大于半径
还有一个参数用来控制倒角的光滑度。

  • BevelSegments :倒角的面数

边缘倒角

通过对法线插值来计算下一条倒角边的两个顶点的位置,然后与前面的边相连形成一个倒角面。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
void FBevelCubeBuilder::GenerateEdgeBevels(const TArray<FVector>& CorePoints)
{
// 边缘倒角定义结构
struct FEdgeBevelDef
{
int32 Core1Idx;
int32 Core2Idx;
FVector Normal1;
FVector Normal2;
};

// 定义所有边缘的倒角数据
TArray<FEdgeBevelDef> EdgeDefs = {
// +X 方向的边缘
{ 0, 1, FVector(0,-1,0), FVector(0,0,-1) },
{ 2, 3, FVector(0,0,-1), FVector(0,1,0) },
{ 4, 5, FVector(0,0,1), FVector(0,-1,0) },
{ 6, 7, FVector(0,1,0), FVector(0,0,1) },

// +Y 方向的边缘
{ 0, 2, FVector(0,0,-1), FVector(-1,0,0) },
{ 1, 3, FVector(1,0,0), FVector(0,0,-1) },
{ 4, 6, FVector(-1,0,0), FVector(0,0,1) },
{ 5, 7, FVector(0,0,1), FVector(1,0,0) },

// +Z 方向的边缘
{ 0, 4, FVector(-1,0,0), FVector(0,-1,0) },
{ 1, 5, FVector(0,-1,0), FVector(1,0,0) },
{ 2, 6, FVector(0,1,0), FVector(-1,0,0) },
{ 3, 7, FVector(1,0,0), FVector(0,1,0) }
};

// 为每条边生成倒角
for (int32 EdgeIndex = 0; EdgeIndex < EdgeDefs.Num(); ++EdgeIndex)
{
const FEdgeBevelDef& EdgeDef = EdgeDefs[EdgeIndex];
EdgeIndex, EdgeDef.Core1Idx, EdgeDef.Core2Idx);

GenerateEdgeStrip(CorePoints, EdgeDef.Core1Idx, EdgeDef.Core2Idx, EdgeDef.Normal1, EdgeDef.Normal2);

EdgeIndex, MeshData.GetVertexCount());
}

MeshData.GetVertexCount(), MeshData.GetTriangleCount());
}

void FBevelCubeBuilder::GenerateEdgeStrip(const TArray<FVector>& CorePoints, int32 Core1Idx, int32 Core2Idx,
const FVector& Normal1, const FVector& Normal2)
{
Core1Idx, Core2Idx);

TArray<int32> PrevStripStartIndices;
TArray<int32> PrevStripEndIndices;

for (int32 s = 0; s <= Params.BevelSections; ++s)
{
const float Alpha = static_cast<float>(s) / Params.BevelSections;
FVector CurrentNormal = FMath::Lerp(Normal1, Normal2, Alpha).GetSafeNormal();

FVector PosStart = CorePoints[Core1Idx] + CurrentNormal * Params.BevelSize;
FVector PosEnd = CorePoints[Core2Idx] + CurrentNormal * Params.BevelSize;

FVector2D UV1(Alpha, 0.0f);
FVector2D UV2(Alpha, 1.0f);

int32 VtxStart = GetOrAddVertex(PosStart, CurrentNormal, UV1);
int32 VtxEnd = GetOrAddVertex(PosEnd, CurrentNormal, UV2);

if (s > 0 && PrevStripStartIndices.Num() > 0 && PrevStripEndIndices.Num() > 0)
{
AddQuad(PrevStripStartIndices[0], PrevStripEndIndices[0], VtxEnd, VtxStart);
}

PrevStripStartIndices = { VtxStart };
PrevStripEndIndices = { VtxEnd };
}
}

角落倒角

思路一样。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
void FBevelCubeBuilder::GenerateCornerBevels(const TArray<FVector>& CorePoints)
{

if (CorePoints.Num() < 8)
{
UE_LOG(LogTemp, Error, TEXT("Invalid CorePoints array size. Expected 8 elements."));
return;
}

// 为每个角落生成倒角
for (int32 CornerIndex = 0; CornerIndex < 8; ++CornerIndex)
{
const FVector& CurrentCorePoint = CorePoints[CornerIndex];
// 这里的四个需要反转渲染方向的倒角是我在实现实现中面的渲染方向反了的。
//这里我没有多调,也许用不同的连接方法可以更优雅一些。
bool bSpecialCornerRenderingOrder = (CornerIndex == 4 || CornerIndex == 7 || CornerIndex == 2 || CornerIndex == 1);


// 计算角落的轴向
const float SignX = FMath::Sign(CurrentCorePoint.X);
const float SignY = FMath::Sign(CurrentCorePoint.Y);
const float SignZ = FMath::Sign(CurrentCorePoint.Z);

const FVector AxisX(SignX, 0.0f, 0.0f);
const FVector AxisY(0.0f, SignY, 0.0f);
const FVector AxisZ(0.0f, 0.0f, SignZ);

// 创建顶点网格
TArray<TArray<int32>> CornerVerticesGrid;
CornerVerticesGrid.SetNum(Params.BevelSections + 1);

for (int32 Lat = 0; Lat <= Params.BevelSections; ++Lat)
{
CornerVerticesGrid[Lat].SetNum(Params.BevelSections + 1 - Lat);
}

// 生成四分之一球体的顶点
for (int32 Lat = 0; Lat <= Params.BevelSections; ++Lat)
{
for (int32 Lon = 0; Lon <= Params.BevelSections - Lat; ++Lon)
{
const float LonAlpha = static_cast<float>(Lon) / Params.BevelSections;
const float LatAlpha = static_cast<float>(Lat) / Params.BevelSections;

TArray<FVector> Vertices = GenerateCornerVertices(CurrentCorePoint, AxisX, AxisY, AxisZ, Lat, Lon);
if (Vertices.Num() > 0)
{
FVector CurrentNormal = (AxisX * (1.0f - LatAlpha - LonAlpha) +
AxisY * LatAlpha +
AxisZ * LonAlpha);
CurrentNormal.Normalize();

FVector2D UV(LonAlpha, LatAlpha);
CornerVerticesGrid[Lat][Lon] = GetOrAddVertex(Vertices[0], CurrentNormal, UV);
}
}
}

// 生成四分之一球体的三角形
for (int32 Lat = 0; Lat < Params.BevelSections; ++Lat)
{
for (int32 Lon = 0; Lon < Params.BevelSections - Lat; ++Lon)
{
GenerateCornerTriangles(CornerVerticesGrid, Lat, Lon, bSpecialCornerRenderingOrder);
}
}

}

}

void FBevelCubeBuilder::GenerateCornerTriangles(const TArray<TArray<int32>>& CornerVerticesGrid,
int32 Lat, int32 Lon, bool bSpecialOrder)
{
const int32 V00 = CornerVerticesGrid[Lat][Lon];
const int32 V10 = CornerVerticesGrid[Lat + 1][Lon];
const int32 V01 = CornerVerticesGrid[Lat][Lon + 1];

if (bSpecialOrder)
{
AddTriangle(V00, V01, V10);
}
else
{
AddTriangle(V00, V10, V01);
}

if (Lon + 1 < CornerVerticesGrid[Lat + 1].Num())
{
const int32 V11 = CornerVerticesGrid[Lat + 1][Lon + 1];

if (bSpecialOrder)
{
AddTriangle(V10, V01, V11);
}
else
{
AddTriangle(V10, V11, V01);
}
}
}