平面上的三维空间#04 | 万物之母 - 三角形

上一篇我们介绍了三维空间到二维平面的 轴测投影 的原理,今天将进一步探索三维空间中的几何图形。


1. 绘制三角形

在第一篇就介绍了绘制空间中 坐标点 的方式, 正所谓: 点动成先,线动成体。有了一点,理论上就可以绘制万物。现在思考一下,如下所示有三个橙色的点,如何在 XOY 平面上绘制三角形:

dart 复制代码
List<Point3D> points = [
  Point3D(-4, 4, 0),
  Point3D(-4, 1, 0),
  Point3D(-1, 1, 0),
];

可以通过根据顶点形成 Path , 绘制路径即可形成符合三维空间的图形。如下所示,三角形在旋转时会保持三维空间的特性:

dart 复制代码
Path path = Path();
List<Offset> point2ds = points.map(project).toList();
path.moveTo(point2ds[0].dx, point2ds[0].dy);
path.lineTo(point2ds[1].dx, point2ds[1].dy);
path.lineTo(point2ds[2].dx, point2ds[2].dy);
path.lineTo(point2ds[0].dx, point2ds[0].dy);
Paint paint = Paint()
  ..color = Colors.cyanAccent
  ..strokeWidth = 2
  ..style = PaintingStyle.stroke;
canvas.drawPath(path, paint);

2. 万物皆为三角形

了解过三维建模或者OpenGL 的朋友可能知道: 所有的体和面都是由三角形拼组而成。既然这里可以画出三维空间中的三角形。那么我有一个大胆的想法,可以基于此绘制复杂的三维形体。

首先要做的一件事是: 封装三角形 的路径绘制。在此之前,先了解一下 OpenGL 标准中三角形的三种绘制方式:

GL_TRIANGLES: 独立三角形

  • 每三个顶点被当作一个三角形。

GL_TRIANGLE_STRIP: 三角形带

  • 每添加一个新顶点,都会创建一个新的三角形,且每次都是通过前两个顶点和当前的新顶点来定义一个三角形

GL_TRIANGLE_FAN: 扇形

第一个顶点是三角形扇形的中心,剩下的每个相邻顶点与中心一起组成一个三角形


3. 封装绘制三角形的方法

现在我们基于 Canvas 中的路径,封装三角形路径的形成方式。三种方式通过 DrawArrays 枚举区分:

dart 复制代码
enum DrawArrays {
  triangles, // 独立三角形
  triangleStrip, // 三角形带
  triangleFan, // 扇形
}

封装 buildPathByPoints3d 方法,传入点列表和绘制三角形的方式:

dart 复制代码
Path buildPathByPoints3d(
  List<Point3D> points3d, {
  DrawArrays type = DrawArrays.triangles,
}) {
  Path path = Path();
  if (points3d.length == 1) path;
  List<Offset> points = points3d.map((project)).toList();
  return switch (type) {
    DrawArrays.triangles => _buildTrianglesPath(points),
    DrawArrays.triangleStrip => _buildStripPath(points),
    DrawArrays.triangleFan => _buildFanPath(points),
  };
}

然后根据三种不同形式的定义,进行具体实现。OpenGL 中之所要分三种绘制三角形的方式,是因为要适应不同场景:

triangles : 可以灵活绘制任何三角形,但是定点数永远是 三角形数*3 ,无法复用顶点。

dart 复制代码
Path _buildTrianglesPath(List<Offset> points) {
  Path path = Path();
  for (int i = 0; i + 2 < points.length; i += 3) {
    path
      ..moveTo(points[i].dx, points[i].dy)
      ..lineTo(points[i + 1].dx, points[i + 1].dy)
      ..lineTo(points[i + 2].dx, points[i + 2].dy)
      ..close();
  }
  return path;
}

triangleStrip : 带状物体,如地形、纸带、绳索等连续结构。相邻三角形顶点可以复用。

dart 复制代码
Path _buildStripPath(List<Offset> points) {
  Path path = Path();
  for (int i = 0; i + 2 < points.length; i++) {
    path.moveTo(points[i].dx, points[i].dy);
    path.lineTo(points[i + 1].dx, points[i + 1].dy);
    path.lineTo(points[i + 2].dx, points[i + 2].dy);
    path.close();
  }
  return path;
}

triangleFan : 圆形、扇形、雷达图等放射状结构。三角形共享中心顶点。

ini 复制代码
Path _buildFanPath(List<Offset> points) {
  Path path = Path();
  Offset center = points[0];
  for (int i = 1; i + 1 < points.length; i++) {
    path.moveTo(center.dx, center.dy);
    path.lineTo(points[i].dx, points[i].dy);
    path.lineTo(points[i + 1].dx, points[i + 1].dy);
    path.close();
  }
  return path;
}

绘制时只需要指定三维空间中的坐标列表即可,比如下面是八个点通过 triangleFan 模式绘制的三角形列表:

dart 复制代码
List<Point3D> points3d = [
  Point3D(4.00, 0.00, 0),
  Point3D(2.83, 2.83, 0),
  Point3D(0.00, 4.00, 0),
  Point3D(-2.83, 2.83, 0),
  Point3D(-4.00, 0.00, 0),
  Point3D(-2.83, -2.83, 0),
  Point3D(0.00, -4.00, 0),
  Point3D(2.83, -2.83, 0),
];

Path path = buildPathByPoints3d(points3d, type: DrawArrays.triangleFan);
canvas.drawPath(path, paint);

4. 项目与源码

后续 《Flutter 伪 3D 绘制系列》 的项目将归属到 toly_game 中,大家可以在 github 开源仓库中得到可以运行的源码。如下所示,TolyGameBox 中已经集成 3D 世界 :

打开后就是可以看到该系列的效果,以后也会在这里继续玩耍 3D 世界。

伪 3d 绘制的代码也是单独分库,在项目中的 toly3d 模块中:


5. 尾声

通过这系列,仅希望探索 2d 绘制 3d 效果的可行性,并基于此介绍一些三维空间的特点。这和真正的 OpenGL 三维绘制是不可同日而语的。目前来看,不依赖与任何三维引擎,可以做到目前的效果,我已经非常满意了。下一篇,将会基于三角形,绘制空间中的其他图形,比如圆、环、多边形、甚至是曲面、三维形体。敬请期待 ~

更多文章和视频知识资讯,大家可以关注我的公众号、掘金和 B 站 。

相关推荐
TT_Close9 小时前
【Flutter×鸿蒙】FVM 不认鸿蒙 SDK?4步手动塞进去
flutter·swift·harmonyos
雨白10 小时前
Android 快捷方式实战指南:静态、动态与固定快捷方式详解
android
hqk10 小时前
鸿蒙项目实战:手把手带你实现 WanAndroid 布局与交互
android·前端·harmonyos
TT_Close11 小时前
【Flutter×鸿蒙】一个"插队"技巧,解决90%的 command not found
flutter·harmonyos
LING11 小时前
RN容器启动优化实践
android·react native
恋猫de小郭13 小时前
Flutter 发布官方 Skills ,Flutter 在 AI 领域再添一助力
android·前端·flutter
李剑一17 小时前
大屏天气展示太普通?视觉升级!用 Canvas 做动态天气遮罩,雷阵雨效果直接封神
前端·vue.js·canvas
Kapaseker19 小时前
一杯美式搞懂 Any、Unit、Nothing
android·kotlin
黄林晴19 小时前
你的 Android App 还没接 AI?Gemini API 接入全攻略
android
恋猫de小郭1 天前
2026 Flutter VS React Native ,同时在 AI 时代 VS Native 开发,你没见过的版本
android·前端·flutter