Flutter中Matrix4旋转时对象背面透明问题求助
解决Flutter中Matrix4变换立方体的背面透明问题
问题原因
- 背面剔除:Flutter的常规2D Widget(如
Container)在3D变换后,背向视图方向的面会被自动剔除,不会进行渲染,导致该面看起来"透明"。 - 绘制顺序冲突:
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
相关产品推荐
相关产品推荐

