Flutter 伪 3D 绘制#02 | 地平面与透视
2025-04-06 08:15 阅读(39)

本文将进一步完善三维空间的视觉表现,如下所示,构建一个地平面网格, 并且具有透视效果:

1. 地平面绘制

在数学的立体几何中,一些辅助线可以更好地帮我们理解三维空间。 这里准备在 X-Y 平面绘制一个网格,体现出地平线,这可以在视觉上让我们更具有归属感:

绘制网格非常简单,主要就是找到坐标,遍历收集线条绘制。如下代码中,遍历 6 次,收集横纵方向上各 6 条线:


void _drawGrid(Canvas canvas) {
  List<(Point3D, Point3D)> lines = [];
  for (double i = 0; i <= 5; i++) {
    lines.add((Point3D(i, 0, 0), Point3D(i, 5, 0)));
    lines.add((Point3D(0, i, 0), Point3D(5, i, 0)));
  }
  
  Paint paint = Paint() ..color = Colors.white..strokeWidth = 1;
  for (var line in lines) {
    Offset p0 = project(line.$1);
    Offset p1 = project(line.$2);
    canvas.drawLine(p0, p1, paint);
  }
}

然后可以修改一下遍历的个数,以及线的长度,就可以轻松实现一个 10*10 的网格:

List<(Point3D, Point3D)> lines = [];
for (double i = -5; i <= 5; i++) {
  lines.add((Point3D(i, -5, 0), Point3D(i, 5, 0)));
  lines.add((Point3D(-5, i, 0), Point3D(5, i, 0)));
}

2. 透视效果

仔细观察不难看出,目前的地平面每条线都是平行的,这对于近大远小的视觉感来说比较为何,特别是旋转角度后,这种绝对的平行会产生违和感:

那么该如何在当前的效果上施加 透视 的魔法呢?如下效果的对比可以看出,施加透视之后,感官上舒适了很多:

透视效果本质上还是在三维点到二维点映射过程,根据视觉规律进行的转换。如下所示,两行代码即可在转换过程中施加透视效果:


// 3D点投影到2D平面
Offset project(Point3D p) {
  double scale = 32.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;
  // 增加透视
  double d = 18.0;
  double radio = d / (d - yProj);
  return Offset(xProj * scale * radio, yProj * scale * radio);
}

当动态修改 d 的值,可以调整透视的效果,如下所示。至于为什么这样就可以实现透视,以及 d 的具体作用。不用着急,后续会一点点分析揭秘:

多绘制一些直线,可以更好地表现地平面和透视的效果。如下所示,在旋转过程中,外部延伸的线条可以联想成路面,越往远处路面线的长度越短,符合透视的规律:

List<(Point3D, Point3D)> lines = [];
for (double i = -32; i <= 32; i++) {
  lines.add((Point3D(i, -5, 0), Point3D(i, 5, 0)));
  lines.add((Point3D(-5, i, 0), Point3D(5, i, 0)));
}

尾声

本想这一篇介绍一下映射原理的,但是发现增加透视会有更好的感官体验,绘制地平面也可以更好地为之后的分析做铺垫。下一篇我将会详细分析一下投影的映射逻辑,敬请期待 ~