Flutter 伪 3D 绘制#1 | 三维空间

最近我在想,使用二维的 Canvas 能否通过投影的方式,模拟三维的坐标系,这样就可以给渲染三维坐标,从而实现简单的伪 3D 效果。如下所示:

绘制一个三维坐标系,通过三维的点绘制了两个平面, 并通过 Slider 交互可以让坐标系沿 z 轴旋转:


1. 定义数据和画板

首先定义一下承载三维点数据的类 Point3D,其中有 x,y,z 三个数值表示坐标的三个维度:

dart 复制代码
class Point3D {
  final double x, y, z;

  Point3D(this.x, this.y, this.z);

  Point3D.zero() : this(0, 0, 0);
}

如下所示,定义 World3D 类继承自 CustomPainter,由于需要让坐标系沿 z 轴旋转,旋转的弧度数据由外界通过构造函数传入。现在关键的两步是:

  • 1\]. 将三维点映射为二维点

dart 复制代码
class World3D extends CustomPainter {

  final double rotationZ;

  World3D({this.rotationZ = 0});

  Offset project(Point3D p) {
    // TODO 将三维点映射为二维点
  }

  @override
  void paint(Canvas canvas, Size size) {
    // TODO 绘制逻辑 
  }


  @override
  bool shouldRepaint(World3D oldDelegate) => rotation != oldDelegate.rotation;
}

2. 如何将三维点映射为二维点

Canvas 绘制绘制的点是 Offset 对象,只有 x,y 两个维度,如下所示的白色坐标系: 想要绘制如下的三维空间,就是将三维点,通过运算转换成二维点,绘制在白色的坐标系上。

下面虽然产生了空间感,但本质上还是二维的线条,只不过按照三维的视觉规律进行投影。所以是一种伪 3d 的绘制方式:

二维 x 轴和三维 x 的夹角是 angle ,通过如下的投影变换,就可以将 Point3D 转换为 Offset

dart 复制代码
Offset project(Point3D p) {
  double scale = 40.0;
  double angle = 30 / 180 * pi;
  final x = (p.x - p.y) * cos(angle);
  final y = (p.x + p.y) * sin(angle) - p.z;
  return Offset(x * scale, y * scale);
}

3. 绘制坐标系

有了三维点的映射关系,就可以很轻松地计算出,在当前三维空间中,某个点坐落在二维空间中的坐标。比如 Point3D(5, 0, 0) 表示 x 轴正方向 5 个点位点的位置,得到位置后,使用 canvas.drawLine 就可以画出原点到 (5, 0, 0) 的直线,也就是下面的红色 x 轴线,其他两个轴类似:

dart 复制代码
@override
void paint(Canvas canvas, Size size) {
  Offset center = Offset(size.width / 2, size.height / 2);
  canvas.translate(center.dx, center.dy);
  // 绘制坐标系轴
  _drawAxis(canvas, Colors.red, Point3D(5, 0, 0), 'X'); // X轴
  _drawAxis(canvas, Colors.green, Point3D(0, 5, 0), 'Y'); // Y轴
  _drawAxis(canvas, Colors.blue, Point3D(0, 0, 5), 'Z'); // Z轴
}

void _drawAxis(Canvas canvas, Color color, Point3D endPoint, String label) {
  Paint paint = Paint()..color = color..strokeWidth = 2;
  Offset start = project(Point3D.zero());
  Offset end = project(endPoint);
  canvas.drawLine(start, end, paint);
  // 绘制轴标签
  TextStyle style = TextStyle(color: color, fontSize: 16);
  TextPainter(
      text: TextSpan(text: label, style: style),
      textDirection: TextDirection.ltr)
    ..layout()
    ..paint(canvas, end + const Offset(5, -10));
}

4. 绘制三维坐标点

同理,只要给出三维坐标,就可以通过 project 映射出二维坐标,通过 Canvas 绘制在平面上。于是我们就完成了任意三维空间点的展示,你也可以自己尝试画一下点,

dart 复制代码
void _drawLines(Canvas canvas) {
  List<(Point3D, Point3D)> bottomLines = [
    (Point3D(0, 0, 0), Point3D(0, 1, 0)),
    (Point3D(0, 0, 0), Point3D(1, 0, 0)),
    (Point3D(0, 0, 0), Point3D(1, 1, 0)),
    (Point3D(0, 1, 0), Point3D(1, 1, 0)),
    (Point3D(1, 0, 0), Point3D(1, 1, 0)),
    (Point3D(0, 1, 0), Point3D(1, 0, 0)),
  ];
  
  Paint paint = Paint() ..color = Colors.white ..strokeWidth = 1;
  
  for (var line in bottomLines) {
    Offset p0 = project(line.$1);
    Offset p1 = project(line.$2);
    canvas.drawLine(p0, p1, paint..color = Colors.white);
  }
}

5. 旋转 z 轴

想要让坐标系根据 z 轴旋转,只要根据旋转角度值,运算 Point3D 在该角度时的在平面上的投影即可,代码如下所示,就可以非常简单地实现下面的旋转效果:

dart 复制代码
Offset project(Point3D p) {
  double scale = 40.0; // 缩放系数
  double angle = 30 / 180 * pi; // 30度弧度值(π/6)
  // 绕Z轴旋转
  final rx = p.x * cos(rotationZ) - p.y * sin(rotationZ);
  final ry = p.x * sin(rotationZ) + p.y * cos(rotationZ);
  // 等轴测投影
  final xProj = (rx - ry) * cos(angle);
  final yProj = (rx + ry) * sin(angle) - p.z;
  return Offset(xProj * scale, yProj * scale);
}

尾声

我没想到,绘制一个伪 3 D 的空间这么简单,其中最重要的 project 方法是 AI 帮忙处理的。下一篇我将会详细分析一下这个投影的映射逻辑,敬请期待 ~

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

相关推荐
TT_Close11 小时前
【Flutter×鸿蒙】FVM 不认鸿蒙 SDK?4步手动塞进去
flutter·swift·harmonyos
雨白12 小时前
Android 快捷方式实战指南:静态、动态与固定快捷方式详解
android
hqk12 小时前
鸿蒙项目实战:手把手带你实现 WanAndroid 布局与交互
android·前端·harmonyos
TT_Close12 小时前
【Flutter×鸿蒙】一个"插队"技巧,解决90%的 command not found
flutter·harmonyos
LING13 小时前
RN容器启动优化实践
android·react native
恋猫de小郭15 小时前
Flutter 发布官方 Skills ,Flutter 在 AI 领域再添一助力
android·前端·flutter
Kapaseker20 小时前
一杯美式搞懂 Any、Unit、Nothing
android·kotlin
黄林晴20 小时前
你的 Android App 还没接 AI?Gemini API 接入全攻略
android
恋猫de小郭1 天前
2026 Flutter VS React Native ,同时在 AI 时代 VS Native 开发,你没见过的版本
android·前端·flutter
冬奇Lab1 天前
PowerManagerService(上):电源状态与WakeLock管理
android·源码阅读