You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter中Matrix4旋转时对象背面透明问题求助

解决Flutter中Matrix4变换立方体的背面透明问题

问题原因

  1. 背面剔除:Flutter的常规2D Widget(如Container)在3D变换后,背向视图方向的面会被自动剔除,不会进行渲染,导致该面看起来"透明"。
  2. 绘制顺序冲突:Stack组件严格按列表顺序绘制Widget(先添加的先绘制,后添加的会覆盖前面的),不会识别3D空间的深度关系,所以即使某个面在3D空间中更靠近镜头,若它在Stack中先被定义,仍会被后定义的面覆盖。

解决方案

步骤1:替换为支持双面渲染的绘制方式

用CustomPaint替代Container绘制立方体的面,因为Canvas绘制不会自动剔除背面,只要变换后的图形在视口中就会被渲染。

步骤2:修正背面的变换逻辑

将原代码中通过invert()生成背面的方式,改为直接平移到z轴负方向并旋转180度,让背面的正面始终朝向可能的视图方向,逻辑更清晰。

步骤3:动态调整绘制顺序

根据旋转角度判断哪个面在3D空间中处于前方,动态调整Stack中Widget的顺序,确保前方的面最后绘制(覆盖后方的面)。

修改后的完整代码

import 'dart:math' as math;
import 'package:flutter/material.dart';

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key, required this.title}) : super(key: key);

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  double dx = 0.0, dy = 0.0, dz = 0.0;

  // 判断蓝色面是否处于前方
  bool get isBlueFront {
    final normalizedDy = dy % (2 * math.pi);
    return normalizedDy < math.pi / 2 || normalizedDy > 3 * math.pi / 2;
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Column(
        crossAxisAlignment: CrossAxisAlignment.center,
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Transform(
            transform: Matrix4.identity()
              ..setEntry(3, 2, 0.001)
              ..rotateX(dx)
              ..rotateY(dy)
              ..rotateZ(dz),
            alignment: Alignment.center,
            child: _BuildBigCube(isBlueFront: isBlueFront),
          ),
          const SizedBox(height: 200),
          _BuildSlider(
            value: dx,
            onChanged: (double value) => setState(() => dx = value),
          ),
          _BuildSlider(
            value: dy,
            onChanged: (double value) => setState(() => dy = value),
          ),
          _BuildSlider(
            value: dz,
            onChanged: (double value) => setState(() => dz = value),
          ),
        ],
      ),
    );
  }
}

class _BuildSlider extends StatelessWidget {
  final double value;
  final Function(double) onChanged;

  const _BuildSlider({Key? key, required this.value, required this.onChanged})
      : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Slider(
      value: value,
      min: -2 * math.pi,
      max: 2 * math.pi,
      onChanged: onChanged,
    );
  }
}

class _BuildBigCube extends StatelessWidget {
  final bool isBlueFront;

  const _BuildBigCube({Key? key, required this.isBlueFront}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    final blueFace = Transform(
      transform: Matrix4.identity()..translate(0.0, 0.0, 100.0),
      alignment: Alignment.center,
      child: CustomPaint(
        size: const Size(200, 200),
        painter: _FacePainter(color: Colors.blue),
        child: const FlutterLogo(size: 200),
      ),
    );

    final orangeFace = Transform(
      transform: Matrix4.identity()
        ..translate(0.0, 0.0, -100.0)
        ..rotateY(math.pi),
      alignment: Alignment.center,
      child: CustomPaint(
        size: const Size(200, 200),
        painter: _FacePainter(color: Colors.orange),
        child: const FlutterLogo(size: 200),
      ),
    );

    // 根据前方面动态调整绘制顺序
    return Stack(children: isBlueFront ? [orangeFace, blueFace] : [blueFace, orangeFace]);
  }
}

class _FacePainter extends CustomPainter {
  final Color color;

  _FacePainter({required this.color});

  @override
  void paint(Canvas canvas, Size size) {
    final paint = Paint()
      ..color = color
      ..style = PaintingStyle.fill;
    canvas.drawRect(Offset.zero & size, paint);
  }

  @override
  bool shouldRepaint(covariant _FacePainter oldDelegate) {
    return oldDelegate.color != color;
  }
}

关键说明

  • CustomPaint通过Canvas绘制矩形,不会自动剔除背面,解决了旋转时面消失的问题。
  • 动态调整Stack的子Widget顺序,确保3D空间中处于前方的面最后绘制,避免了2D绘制顺序覆盖3D深度的问题。
  • 背面的变换改为translate(0,0,-100)..rotateY(pi),让橙色面的正面始终朝向外侧,逻辑更直观。

内容的提问来源于stack exchange,提问作者Titas Černiauskas

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.21 15:25:44