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 站 。

相关推荐
里欧跑得慢6 小时前
15. Web可访问性最佳实践:让每个用户都能平等访问
前端·css·flutter·web
Lanren的编程日记9 小时前
Flutter 鸿蒙应用数据版本管理实战:版本记录+版本回退+版本对比,实现全链路数据版本控制
flutter·华为·harmonyos
饭小猿人12 小时前
Android 腾讯X5WebView如何禁止系统自带剪切板和自定义剪切板视图
android·java
_李小白12 小时前
【android opencv学习笔记】Day 8: remap(像素位置重映射)
android·opencv·学习
美狐美颜SDK开放平台12 小时前
多场景美颜SDK解决方案:直播APP(iOS/安卓)开发接入详解
android·人工智能·ios·音视频·美颜sdk·第三方美颜sdk·短视频美颜sdk
嗷o嗷o13 小时前
Android BLE 里,MTU、分包和长数据发送到底该怎么处理
android
Gary Studio14 小时前
Android AIDL HAL工程结构示例
android
y = xⁿ15 小时前
MySQL八股知识合集
android·mysql·adb
MonkeyKing15 小时前
Flutter列表性能极致优化:从卡顿到丝滑
flutter·dart
IntMainJhy15 小时前
「Flutter三方库sqflite的鸿蒙化适配与实战指南:从入门到踩坑的本地数据库开发全记录」
数据库·flutter·华为·信息可视化·数据库开发·harmonyos