Flutter for Harmony 跨平台开发实战:光线追踪——折射反射的光学模拟

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net


一、📐 光线追踪基础

1.1 几何光学基本原理

光线追踪(Ray Tracing)是一种基于几何光学的渲染技术,通过追踪光线在场景中的传播路径来模拟光与物体的相互作用。与光栅化渲染不同,光线追踪能够自然地处理反射、折射、阴影等光学现象。

光线追踪的核心思想

  1. 从摄像机发射光线穿过每个像素
  2. 计算光线与场景中物体的交点
  3. 在交点处计算反射和折射光线
  4. 递归追踪这些次级光线
  5. 累积光照贡献直到满足终止条件

1.2 斯涅尔定律

斯涅尔定律(Snell's Law)描述了光线在两种不同介质界面上的折射行为:

n 1 sin ⁡ θ 1 = n 2 sin ⁡ θ 2 n_1 \sin\theta_1 = n_2 \sin\theta_2 n1sinθ1=n2sinθ2

其中:

  • n 1 , n 2 n_1, n_2 n1,n2:两种介质的折射率
  • θ 1 \theta_1 θ1:入射角(光线与法线的夹角)
  • θ 2 \theta_2 θ2:折射角

折射率的物理意义

折射率 n n n 定义为光在真空中的速度 c c c 与光在介质中的速度 v v v 的比值:

n = c v n = \frac{c}{v} n=vc

常见介质的折射率:

介质 折射率
真空 1.0
空气 1.0003
1.33
玻璃 1.5-1.9
钻石 2.42

1.3 反射定律

反射定律指出,入射光线、法线和反射光线位于同一平面内,且入射角等于反射角:

θ i = θ r \theta_i = \theta_r θi=θr

反射向量计算

给定入射向量 i ⃗ \vec{i} i 和法向量 n ⃗ \vec{n} n (单位向量),反射向量 r ⃗ \vec{r} r 可通过以下公式计算:

r ⃗ = i ⃗ − 2 ( i ⃗ ⋅ n ⃗ ) n ⃗ \vec{r} = \vec{i} - 2(\vec{i} \cdot \vec{n})\vec{n} r =i −2(i ⋅n )n

1.4 全内反射

当光线从光密介质进入光疏介质时,如果入射角超过临界角,则发生全内反射(Total Internal Reflection)。

临界角计算

θ c = arcsin ⁡ ( n 2 n 1 ) \theta_c = \arcsin\left(\frac{n_2}{n_1}\right) θc=arcsin(n1n2)

其中 n 1 > n 2 n_1 > n_2 n1>n2。全内反射是光纤通信和某些光学器件的基础原理。


二、💻 二维光线追踪实现

2.1 基础数据结构

向量类 V2

向量是光线追踪中最基础的数学工具,用于表示位置、方向和法线。

dart 复制代码
class V2 {
  final double x, y;
  const V2(this.x, this.y);
  
  double get len => sqrt(x * x + y * y);
  V2 get norm => len < 1e-10 ? const V2(0, 0) : V2(x / len, y / len);
  
  V2 operator +(V2 o) => V2(x + o.x, y + o.y);
  V2 operator -(V2 o) => V2(x - o.x, y - o.y);
  V2 operator -() => V2(-x, -y);
  V2 operator *(double s) => V2(x * s, y * s);
  
  double dot(V2 o) => x * o.x + y * o.y;
  V2 reflect(V2 n) => this - n * (2 * dot(n));
  
  Offset toOffset() => Offset(x, y);
}

向量运算说明

运算 数学表达 物理意义
模长 ∣ v ⃗ ∣ = x 2 + y 2 |\vec{v}| = \sqrt{x^2 + y^2} ∣v ∣=x2+y2 向量长度
归一化 v ^ = v ⃗ ∣ v ⃗ ∣ \hat{v} = \frac{\vec{v}}{|\vec{v}|} v^=∣v ∣v 单位方向
点积 a ⃗ ⋅ b ⃗ = a x b x + a y b y \vec{a} \cdot \vec{b} = a_x b_x + a_y b_y a ⋅b =axbx+ayby 角度余弦、投影
反射 r ⃗ = i ⃗ − 2 ( i ⃗ ⋅ n ⃗ ) n ⃗ \vec{r} = \vec{i} - 2(\vec{i} \cdot \vec{n})\vec{n} r =i −2(i ⋅n )n 镜面反射
光线类 Ray2D

光线由起点和方向定义:

dart 复制代码
class Ray2D {
  final V2 origin, dir;
  const Ray2D(this.origin, this.dir);
  
  V2 at(double t) => origin + dir * t;
}

光线上的任意点可以通过参数 t t t 表示:

P ⃗ ( t ) = O ⃗ + t D ⃗ \vec{P}(t) = \vec{O} + t\vec{D} P (t)=O +tD

其中 O ⃗ \vec{O} O 是原点, D ⃗ \vec{D} D 是方向向量, t ≥ 0 t \geq 0 t≥0。

2.2 几何图元

圆形(圆形透镜/障碍物)
dart 复制代码
class Circle {
  final V2 center;
  final double radius;
  final double refractiveIndex;
  
  const Circle({
    required this.center,
    required this.radius,
    this.refractiveIndex = 1.5,
  });
  
  double? intersect(Ray2D ray) {
    final oc = ray.origin - center;
    final a = ray.dir.dot(ray.dir);
    final b = 2 * oc.dot(ray.dir);
    final c = oc.dot(oc) - radius * radius;
  
    final discriminant = b * b - 4 * a * c;
    if (discriminant < 0) return null;
  
    final t1 = (-b - sqrt(discriminant)) / (2 * a);
    final t2 = (-b + sqrt(discriminant)) / (2 * a);
  
    if (t1 > 1e-6) return t1;
    if (t2 > 1e-6) return t2;
    return null;
  }
  
  V2 getNormal(V2 point) => (point - center).norm;
}

圆线相交推导

圆的方程: ( x − c x ) 2 + ( y − c y ) 2 = r 2 (x - c_x)^2 + (y - c_y)^2 = r^2 (x−cx)2+(y−cy)2=r2

光线参数方程: P ⃗ ( t ) = O ⃗ + t D ⃗ \vec{P}(t) = \vec{O} + t\vec{D} P (t)=O +tD

代入得:

∥ O ⃗ + t D ⃗ − C ⃗ ∥ 2 = r 2 \|\vec{O} + t\vec{D} - \vec{C}\|^2 = r^2 ∥O +tD −C ∥2=r2

展开后得到关于 t t t 的二次方程:

a t 2 + b t + c = 0 at^2 + bt + c = 0 at2+bt+c=0

其中:

  • a = D ⃗ ⋅ D ⃗ = 1 a = \vec{D} \cdot \vec{D} = 1 a=D ⋅D =1(归一化方向)
  • b = 2 ( O ⃗ − C ⃗ ) ⋅ D ⃗ b = 2(\vec{O} - \vec{C}) \cdot \vec{D} b=2(O −C )⋅D
  • c = ∥ O ⃗ − C ⃗ ∥ 2 − r 2 c = \|\vec{O} - \vec{C}\|^2 - r^2 c=∥O −C ∥2−r2

判别式: Δ = b 2 − 4 a c \Delta = b^2 - 4ac Δ=b2−4ac

当 Δ ≥ 0 \Delta \geq 0 Δ≥0 时有交点,取最小的正 t t t 值。

线段(平面镜)
dart 复制代码
class LineSegment {
  final V2 p1, p2;
  const LineSegment(this.p1, this.p2);
  
  double? intersect(Ray2D ray) {
    final v1 = ray.origin - p1;
    final v2 = p2 - p1;
    final v3 = V2(-ray.dir.y, ray.dir.x);
  
    final dot = v2.dot(v3);
    if (dot.abs() < 1e-10) return null;
  
    final t1 = v2.cross(v1) / dot;
    final t2 = v1.dot(v3) / dot;
  
    if (t1 > 1e-6 && t2 >= 0 && t2 <= 1) return t1;
    return null;
  }
}

extension on V2 {
  double cross(V2 o) => x * o.y - y * o.x;
}

2.3 光线追踪引擎

dart 复制代码
class RayTracer {
  final List<Circle> circles;
  final List<LineSegment> mirrors;
  final int maxBounces;
  
  RayTracer({
    required this.circles,
    required this.mirrors,
    this.maxBounces = 10,
  });
  
  List<V2> trace(Ray2D ray) {
    final points = [ray.origin];
    var currentRay = ray;
  
    for (int i = 0; i < maxBounces; i++) {
      double? closestT = double.infinity;
      V2? closestPoint;
      V2? closestNormal;
      bool hitMirror = false;
    
      // 检查圆的交点
      for (final circle in circles) {
        final t = circle.intersect(currentRay);
        if (t != null && t < closestT) {
          closestT = t;
          closestPoint = currentRay.at(t);
          closestNormal = circle.getNormal(closestPoint!);
          hitMirror = false;
        }
      }
    
      // 检查镜子的交点
      for (final mirror in mirrors) {
        final t = mirror.intersect(currentRay);
        if (t != null && t < closestT) {
          closestT = t;
          closestPoint = currentRay.at(t);
          // 计算线段的法线
          final dx = mirror.p2.x - mirror.p1.x;
          final dy = mirror.p2.y - mirror.p1.y;
          closestNormal = V2(-dy, dx).norm;
          hitMirror = true;
        }
      }
    
      if (closestPoint == null) break;
    
      points.add(closestPoint!);
    
      // 计算反射或折射
      if (hitMirror) {
        final reflected = currentRay.dir.reflect(closestNormal!);
        currentRay = Ray2D(closestPoint, reflected);
      } else {
        // 折射处理
        final refracted = refract(currentRay.dir, closestNormal!, 1.0, 1.5);
        if (refracted == null) break; // 全内反射
        currentRay = Ray2D(closestPoint, refracted);
      }
    }
  
    return points;
  }
  
  V2? refract(V2 incident, V2 normal, double n1, double n2) {
    final n = n1 / n2;
    final cosI = -incident.dot(normal);
    final sinT2 = n * n * (1 - cosI * cosI);
  
    if (sinT2 > 1) return null; // 全内反射
  
    final cosT = sqrt(1 - sinT2);
    return incident * n + normal * (n * cosI - cosT);
  }
}

2.4 可视化组件

dart 复制代码
class RayTracingPainter extends CustomPainter {
  final List<V2> path;
  final List<Circle> circles;
  final List<LineSegment> mirrors;
  
  RayTracingPainter({
    required this.path,
    required this.circles,
    required this.mirrors,
  });
  
  @override
  void paint(Canvas canvas, Size size) {
    // 绘制背景
    canvas.drawRect(Rect.fromLTWH(0, 0, size.width, size.height),
        Paint()..color = const Color(0xFF0F172A));
  
    // 绘制圆形
    for (final circle in circles) {
      canvas.drawCircle(
        circle.center.toOffset(),
        circle.radius,
        Paint()
          ..color = Colors.blue.withOpacity(0.3)
          ..style = PaintingStyle.fill,
      );
      canvas.drawCircle(
        circle.center.toOffset(),
        circle.radius,
        Paint()
          ..color = Colors.blue
          ..strokeWidth = 2
          ..style = PaintingStyle.stroke,
      );
    }
  
    // 绘制镜子
    for (final mirror in mirrors) {
      canvas.drawLine(
        mirror.p1.toOffset(),
        mirror.p2.toOffset(),
        Paint()
          ..color = Colors.cyan
          ..strokeWidth = 3,
      );
    }
  
    // 绘制光线路径
    if (path.length >= 2) {
      final paint = Paint()
        ..color = Colors.amber
        ..strokeWidth = 2
        ..strokeCap = StrokeCap.round;
    
      for (int i = 0; i < path.length - 1; i++) {
        canvas.drawLine(
          path[i].toOffset(),
          path[i + 1].toOffset(),
          paint,
        );
      }
    
      // 绘制交点
      for (int i = 1; i < path.length - 1; i++) {
        canvas.drawCircle(
          path[i].toOffset(),
          4,
          Paint()
            ..color = Colors.amber
            ..style = PaintingStyle.fill,
        );
      }
    }
  }
  
  @override
  bool shouldRepaint(covariant RayTracingPainter old) => true;
}

三、🎯 完整示例应用

3.1 主界面结构

dart 复制代码
void main() {
  runApp(const RayTracingApp());
}

class RayTracingApp extends StatelessWidget {
  const RayTracingApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: '光线追踪',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(
          seedColor: Colors.amber,
          brightness: Brightness.dark,
        ),
        useMaterial3: true,
      ),
      home: const RayTracingHomePage(),
      debugShowCheckedModeBanner: false,
    );
  }
}

class RayTracingHomePage extends StatelessWidget {
  const RayTracingHomePage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('💡 光线追踪'),
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
      ),
      body: ListView(
        padding: const EdgeInsets.all(16),
        children: [
          _buildCard(
            context,
            title: '2D光线追踪',
            description: '基础光线追踪演示',
            icon: Icons.lightbulb,
            color: Colors.amber,
            onTap: () => Navigator.push(
              context,
              MaterialPageRoute(builder: (_) => const RayTracing2DDemo()),
            ),
          ),
          _buildCard(
            context,
            title: '镜面反射',
            description: '多次反射效果',
            icon: Icons.flip,
            color: Colors.cyan,
            onTap: () => Navigator.push(
              context,
              MaterialPageRoute(builder: (_) => const MirrorDemo()),
            ),
          ),
          _buildCard(
            context,
            title: '棱镜折射',
            description: '色散与光谱',
            icon: Icons.change_history,
            color: Colors.purple,
            onTap: () => Navigator.push(
              context,
              MaterialPageRoute(builder: (_) => const PrismDemo()),
            ),
          ),
          _buildCard(
            context,
            title: '球面透镜',
            description: '凸透镜成像',
            icon: Icons.circle,
            color: Colors.teal,
            onTap: () => Navigator.push(
              context,
              MaterialPageRoute(builder: (_) => const LensDemo()),
            ),
          ),
        ],
      ),
    );
  }

  Widget _buildCard(
    BuildContext context, {
    required String title,
    required String description,
    required IconData icon,
    required Color color,
    required VoidCallback onTap,
  }) {
    return Card(
      margin: const EdgeInsets.only(bottom: 12),
      shape: RoundedRectangleBorder(
        borderRadius: BorderRadius.circular(16),
      ),
      child: InkWell(
        onTap: onTap,
        borderRadius: BorderRadius.circular(16),
        child: Padding(
          padding: const EdgeInsets.all(16),
          child: Row(
            children: [
              Container(
                width: 56,
                height: 56,
                decoration: BoxDecoration(
                  color: color.withOpacity(0.1),
                  borderRadius: BorderRadius.circular(12),
                ),
                child: Icon(icon, color: color, size: 28),
              ),
              const SizedBox(width: 16),
              Expanded(
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(
                      title,
                      style: const TextStyle(
                        fontSize: 16,
                        fontWeight: FontWeight.bold,
                      ),
                    ),
                    const SizedBox(height: 4),
                    Text(
                      description,
                      style: TextStyle(
                        color: Colors.grey[600],
                        fontSize: 14,
                      ),
                    ),
                  ],
                ),
              ),
              Icon(Icons.chevron_right, color: Colors.grey[400]),
            ],
          ),
        ),
      ),
    );
  }
}

3.2 2D光线追踪演示

dart 复制代码
class RayTracing2DDemo extends StatefulWidget {
  const RayTracing2DDemo({super.key});
  
  @override
  State<RayTracing2DDemo> createState() => _RayTracing2DDemoState();
}

class _RayTracing2DDemoState extends State<RayTracing2DDemo> {
  double _angle = 0;
  List<V2> _path = [];
  
  late RayTracer _tracer;
  
  @override
  void initState() {
    super.initState();
    _setupScene();
  }
  
  void _setupScene() {
    _tracer = RayTracer(
      circles: [
        Circle(center: const V2(200, 200), radius: 80, refractiveIndex: 1.5),
      ],
      mirrors: [
        LineSegment(const V2(50, 100), const V2(150, 50)),
        LineSegment(const V2(350, 50), const V2(450, 100)),
      ],
      maxBounces: 5,
    );
    _updatePath();
  }
  
  void _updatePath() {
    final cx = 200.0;
    final cy = 400.0;
    final dir = V2(0, -1);
  
    final rotated = V2(
      dir.x * cos(_angle) - dir.y * sin(_angle),
      dir.x * sin(_angle) + dir.y * cos(_angle),
    );
  
    _path = _tracer.trace(Ray2D(V2(cx, cy), rotated.norm));
    setState(() {});
  }
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('2D光线追踪')),
      body: Column(
        children: [
          Expanded(
            child: CustomPaint(
              painter: RayTracingPainter(
                path: _path,
                circles: _tracer.circles,
                mirrors: _tracer.mirrors,
              ),
              size: Size.infinite,
            ),
          ),
          _buildControls(),
        ],
      ),
    );
  }
  
  Widget _buildControls() {
    return Container(
      padding: const EdgeInsets.all(16),
      decoration: BoxDecoration(
        color: Colors.grey[900],
        borderRadius: const BorderRadius.vertical(
          top: Radius.circular(20),
        ),
      ),
      child: Column(
        mainAxisSize: MainAxisSize.min,
        children: [
          Row(
            children: [
              const Text('入射角: ', style: TextStyle(color: Colors.white70)),
              Expanded(
                child: Slider(
                  value: _angle,
                  min: -pi / 4,
                  max: pi / 4,
                  onChanged: (v) {
                    _angle = v;
                    _updatePath();
                  },
                ),
              ),
              Text('${(_angle * 180 / pi).toStringAsFixed(1)}°',
                  style: const TextStyle(color: Colors.amber)),
            ],
          ),
        ],
      ),
    );
  }
}

3.3 镜面反射演示

dart 复制代码
class MirrorDemo extends StatefulWidget {
  const MirrorDemo({super.key});
  
  @override
  State<MirrorDemo> createState() => _MirrorDemoState();
}

class _MirrorDemoState extends State<MirrorDemo> {
  double _mirrorAngle = 0;
  List<V2> _path = [];
  
  late RayTracer _tracer;
  
  @override
  void initState() {
    super.initState();
    _setupScene();
  }
  
  void _setupScene() {
    final cx = 200.0;
    final cy = 300.0;
  
    final mirrorLen = 150.0;
    final mx = cx - mirrorLen / 2;
    final my = cy;
  
    _tracer = RayTracer(
      circles: [],
      mirrors: [
        LineSegment(
          V2(mx, my),
          V2(mx + mirrorLen * cos(_mirrorAngle), my + mirrorLen * sin(_mirrorAngle)),
        ),
      ],
      maxBounces: 3,
    );
  
    _updatePath();
  }
  
  void _updatePath() {
    final cx = 200.0;
    final cy = 100.0;
    final dir = V2(0, 1);
  
    _path = _tracer.trace(Ray2D(V2(cx, cy), dir.norm));
    setState(() {});
  }
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('镜面反射')),
      body: Column(
        children: [
          Expanded(
            child: CustomPaint(
              painter: RayTracingPainter(
                path: _path,
                circles: _tracer.circles,
                mirrors: _tracer.mirrors,
              ),
              size: Size.infinite,
            ),
          ),
          _buildControls(),
        ],
      ),
    );
  }
  
  Widget _buildControls() {
    return Container(
      padding: const EdgeInsets.all(16),
      decoration: BoxDecoration(
        color: Colors.grey[900],
        borderRadius: const BorderRadius.vertical(
          top: Radius.circular(20),
        ),
      ),
      child: Row(
        children: [
          const Text('镜子角度: ', style: TextStyle(color: Colors.white70)),
          Expanded(
            child: Slider(
              value: _mirrorAngle,
              min: -pi / 3,
              max: pi / 3,
              onChanged: (v) {
                setState(() {
                  _mirrorAngle = v;
                  _setupScene();
                });
              },
            ),
          ),
          Text('${(_mirrorAngle * 180 / pi).toStringAsFixed(1)}°',
              style: const TextStyle(color: Colors.cyan)),
        ],
      ),
    );
  }
}

3.4 棱镜折射演示

dart 复制代码
class PrismDemo extends StatefulWidget {
  const PrismDemo({super.key});
  
  @override
  State<PrismDemo> createState() => _PrismDemoState();
}

class _PrismDemoState extends State<PrismDemo> {
  double _prismAngle = 0;
  List<V2> _path = [];
  
  late RayTracer _tracer;
  
  @override
  void initState() {
    super.initState();
    _setupScene();
  }
  
  void _setupScene() {
    final cx = 200.0;
    final cy = 300.0;
  
    final prismRadius = 80.0;
    _tracer = RayTracer(
      circles: [
        Circle(
          center: V2(cx, cy),
          radius: prismRadius,
          refractiveIndex: 1.5,
        ),
      ],
      mirrors: [],
      maxBounces: 3,
    );
  
    _updatePath();
  }
  
  void _updatePath() {
    final cx = 200.0;
    final cy = 100.0;
    final dir = V2(0, 1);
  
    _path = _tracer.trace(Ray2D(V2(cx, cy), dir.norm));
    setState(() {});
  }
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('棱镜折射')),
      body: Column(
        children: [
          Expanded(
            child: CustomPaint(
              painter: RayTracingPainter(
                path: _path,
                circles: _tracer.circles,
                mirrors: _tracer.mirrors,
              ),
              size: Size.infinite,
            ),
          ),
          _buildControls(),
        ],
      ),
    );
  }
  
  Widget _buildControls() {
    return Container(
      padding: const EdgeInsets.all(16),
      decoration: BoxDecoration(
        color: Colors.grey[900],
        borderRadius: const BorderRadius.vertical(
          top: Radius.circular(20),
        ),
      ),
      child: Row(
        children: [
          const Text('入射位置: ', style: TextStyle(color: Colors.white70)),
          Expanded(
            child: Slider(
              value: _prismAngle,
              min: -50,
              max: 50,
              onChanged: (v) {
                _prismAngle = v;
                _updatePath();
              },
            ),
          ),
          Text('${_prismAngle.toStringAsFixed(0)}',
              style: const TextStyle(color: Colors.purple)),
        ],
      ),
    );
  }
}

3.5 球面透镜演示

dart 复制代码
class LensDemo extends StatefulWidget {
  const LensDemo({super.key});
  
  @override
  State<LensDemo> createState() => _LensDemoState();
}

class _LensDemoState extends State<LensDemo> {
  double _lensPower = 1.0;
  List<V2> _path = [];
  
  late RayTracer _tracer;
  
  @override
  void initState() {
    super.initState();
    _setupScene();
  }
  
  void _setupScene() {
    final cx = 200.0;
    final cy = 300.0;
  
    final lensRadius = 100.0 / _lensPower;
    _tracer = RayTracer(
      circles: [
        Circle(
          center: V2(cx, cy),
          radius: lensRadius,
          refractiveIndex: 1.5,
        ),
      ],
      mirrors: [],
      maxBounces: 2,
    );
  
    _updatePath();
  }
  
  void _updatePath() {
    final cx = 200.0;
    final cy = 100.0;
    final dir = V2(0, 1);
  
    _path = _tracer.trace(Ray2D(V2(cx, cy), dir.norm));
    setState(() {});
  }
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('球面透镜')),
      body: Column(
        children: [
          Expanded(
            child: CustomPaint(
              painter: RayTracingPainter(
                path: _path,
                circles: _tracer.circles,
                mirrors: _tracer.mirrors,
              ),
              size: Size.infinite,
            ),
          ),
          _buildControls(),
        ],
      ),
    );
  }
  
  Widget _buildControls() {
    return Container(
      padding: const EdgeInsets.all(16),
      decoration: BoxDecoration(
        color: Colors.grey[900],
        borderRadius: const BorderRadius.vertical(
          top: Radius.circular(20),
        ),
      ),
      child: Row(
        children: [
          const Text('透镜度数: ', style: TextStyle(color: Colors.white70)),
          Expanded(
            child: Slider(
              value: _lensPower,
              min: 0.5,
              max: 2.0,
              onChanged: (v) {
                setState(() {
                  _lensPower = v;
                  _setupScene();
                });
              },
            ),
          ),
          Text(_lensPower.toStringAsFixed(1),
              style: const TextStyle(color: Colors.teal)),
        ],
      ),
    );
  }
}

四、🔬 数学原理深入

4.1 折射向量推导

根据斯涅尔定律,我们需要计算折射向量的方向。设入射向量 i ⃗ \vec{i} i 、法向量 n ⃗ \vec{n} n (单位向量),折射率比 r = n 1 / n 2 r = n_1/n_2 r=n1/n2。

推导过程

  1. 计算入射角的余弦:

    cos ⁡ θ 1 = − i ⃗ ⋅ n ⃗ \cos\theta_1 = -\vec{i} \cdot \vec{n} cosθ1=−i ⋅n

  2. 应用斯涅尔定律:

    sin ⁡ θ 2 = n 1 n 2 sin ⁡ θ 1 \sin\theta_2 = \frac{n_1}{n_2} \sin\theta_1 sinθ2=n2n1sinθ1

  3. 由于 sin ⁡ 2 θ + cos ⁡ 2 θ = 1 \sin^2\theta + \cos^2\theta = 1 sin2θ+cos2θ=1:

    sin ⁡ 2 θ 2 = r 2 ( 1 − cos ⁡ 2 θ 1 ) \sin^2\theta_2 = r^2 (1 - \cos^2\theta_1) sin2θ2=r2(1−cos2θ1)

  4. 计算折射角的余弦:

    cos ⁡ θ 2 = 1 − r 2 ( 1 − cos ⁡ 2 θ 1 ) \cos\theta_2 = \sqrt{1 - r^2 (1 - \cos^2\theta_1)} cosθ2=1−r2(1−cos2θ1)

  5. 折射向量公式:

    t ⃗ = r i ⃗ + ( r cos ⁡ θ 1 − cos ⁡ θ 2 ) n ⃗ \vec{t} = r\vec{i} + (r\cos\theta_1 - \cos\theta_2)\vec{n} t =ri +(rcosθ1−cosθ2)n

全内反射条件

当 r > 1 r > 1 r>1(从光密到光疏)时,如果:

r 2 ( 1 − cos ⁡ 2 θ 1 ) > 1 r^2 (1 - \cos^2\theta_1) > 1 r2(1−cos2θ1)>1

则发生全内反射,折射光线不存在。

4.2 菲涅尔方程

菲涅尔方程描述了反射和折射光强的分配:

垂直偏振分量(s偏振)

R s = ∣ n 1 cos ⁡ θ 1 − n 2 cos ⁡ θ 2 n 1 cos ⁡ θ 1 + n 2 cos ⁡ θ 2 ∣ 2 R_s = \left|\frac{n_1\cos\theta_1 - n_2\cos\theta_2}{n_1\cos\theta_1 + n_2\cos\theta_2}\right|^2 Rs= n1cosθ1+n2cosθ2n1cosθ1−n2cosθ2 2

平行偏振分量(p偏振)

R p = ∣ n 1 cos ⁡ θ 2 − n 2 cos ⁡ θ 1 n 1 cos ⁡ θ 2 + n 2 cos ⁡ θ 1 ∣ 2 R_p = \left|\frac{n_1\cos\theta_2 - n_2\cos\theta_1}{n_1\cos\theta_2 + n_2\cos\theta_1}\right|^2 Rp= n1cosθ2+n2cosθ1n1cosθ2−n2cosθ1 2

平均反射率

R = R s + R p 2 R = \frac{R_s + R_p}{2} R=2Rs+Rp

透射率: T = 1 − R T = 1 - R T=1−R

4.3 色散现象

折射率与波长的关系导致色散:

n ( λ ) = A + B λ 2 n(\lambda) = A + \frac{B}{\lambda^2} n(λ)=A+λ2B

其中 A A A 和 B B B 是材料常数。

柯西公式(适用于可见光):

n ( λ ) = A + B λ 2 + C λ 4 n(\lambda) = A + \frac{B}{\lambda^2} + \frac{C}{\lambda^4} n(λ)=A+λ2B+λ4C

不同波长的光折射率不同,导致白光通过棱镜后分解为光谱。


五、🚀 高级应用

5.1 光纤模拟

光纤利用全内反射原理传输光信号。临界角:

θ c = arcsin ⁡ ( n c l a d d i n g n c o r e ) \theta_c = \arcsin\left(\frac{n_{cladding}}{n_{core}}\right) θc=arcsin(ncorencladding)

其中 n c o r e > n c l a d d i n g n_{core} > n_{cladding} ncore>ncladding。

5.2 透镜成像

薄透镜公式:

1 f = ( n − 1 ) ( 1 R 1 − 1 R 2 ) \frac{1}{f} = (n - 1)\left(\frac{1}{R_1} - \frac{1}{R_2}\right) f1=(n−1)(R11−R21)

其中:

  • f f f:焦距
  • n n n:透镜折射率
  • R 1 , R 2 R_1, R_2 R1,R2:透镜表面的曲率半径

成像公式:

1 d o + 1 d i = 1 f \frac{1}{d_o} + \frac{1}{d_i} = \frac{1}{f} do1+di1=f1

5.3 鸿蒙多端适配

dart 复制代码
class RayTracingConfig {
  static int getMaxBounces(BuildContext context) {
    final performance = DevicePerformance.getLevel();
    switch (performance) {
      case PerformanceLevel.high: return 10;
      case PerformanceLevel.medium: return 5;
      case PerformanceLevel.low: return 3;
    }
  }
  
  static double getStepSize(BuildContext context) {
    final performance = DevicePerformance.getLevel();
    switch (performance) {
      case PerformanceLevel.high: return 1.0;
      case PerformanceLevel.medium: return 2.0;
      case PerformanceLevel.low: return 3.0;
    }
  }
}

六、⚡ 性能优化

6.1 空间加速结构

使用包围体层次结构(BVH)加速相交测试:

方法 时间复杂度 适用场景
暴力搜索 O(n) 少量物体
网格划分 O(log n) 均匀分布
BVH O(log n) 复杂场景
kd-tree O(log n) 动态场景

6.2 光线缓存

缓存常用光线的计算结果:

dart 复制代码
class RayCache {
  static final Map<String, List<V2>> _cache = {};
  
  static List<V2> get(Ray2D ray, RayTracer tracer) {
    final key = '${ray.origin.x}_${ray.origin.y}_${ray.dir.x}_${ray.dir.y}';
    return _cache.putIfAbsent(key, () => tracer.trace(ray));
  }
  
  static void clear() => _cache.clear();
}

七、📚 学习资源

推荐阅读

主题 资源 难度
计算机图形学 《计算机图形学》Peter Shirley ⭐⭐⭐
光学 《光学原理》Hecht ⭐⭐
光线追踪 《Ray Tracing in One Weekend》 ⭐⭐
数学基础 《线性代数及其应用》

相关项目

  • Mitsuba Renderer:物理正确渲染器
  • PBRT:Physically Based Rendering
  • LuxRender:开源渲染引擎

八、📝 总结

本文深入探讨了光线追踪的数学原理及其在 Flutter 中的实现。

核心知识点

知识点 说明
📐斯涅尔定律 折射现象的基础
🔄反射定律 镜面反射的计算
💡光线追踪 光传播路径模拟
🌈色散 折射率与波长的关系
🔮全内反射 光纤通信基础

最佳实践

  • ✅ 使用归一化向量进行计算
  • ✅ 合理设置最大反射次数
  • ✅ 使用空间加速结构
  • ✅ 考虑菲涅尔效应

进阶方向

  • 🔮 三维光线追踪
  • ✨ 路径追踪
  • 📊 光子映射
  • 🎨 实时渲染

💡 提示:本文代码基于 Flutter for Harmony 开发,可在鸿蒙设备上流畅运行。

相关推荐
早點睡3903 小时前
Flutter for Harmony 跨平台开发实战:鸿蒙与音乐律动艺术、FFT 频谱能量场:正弦函数的叠加艺术
flutter·华为·harmonyos
2601_949593653 小时前
Flutter for Harmony 跨平台开发实战:德劳内三角剖分——点集连接的几何美学
flutter
lili-felicity3 小时前
基础入门 Flutter for OpenHarmony:三方库实战 flutter_lifecycle_detector 生命周期检测详解
flutter
2601_949593653 小时前
Flutter for Harmony 跨平台开发实战:双曲几何与庞加莱圆盘——非欧空间的视觉映射
flutter
松叶似针3 小时前
Flutter三方库适配OpenHarmony【doc_text】— parseDocxXml:正则驱动的 XML 文本提取
xml·flutter
lili-felicity3 小时前
基础入门 Flutter for OpenHarmony:三方库实战 flutter_phone_direct_caller 电话拨号详解
flutter
不爱吃糖的程序媛4 小时前
Flutter-OH 插件适配 HarmonyOS 实战:以屏幕方向控制为例
flutter·华为·harmonyos
松叶似针4 小时前
Flutter三方库适配OpenHarmony【doc_text】— 文件格式路由:.doc 与 .docx 的分流策略
flutter·harmonyos
阿林来了4 小时前
Flutter三方库适配OpenHarmony【flutter_web_auth】— FlutterPlugin 与 AbilityAware 双接口实现
flutter·harmonyos