본문 바로가기
Flutter/Flutter 필수개념

[Flutter] Matrix4 이해하기

by 붕어사랑 티스토리 2021. 12. 15.
반응형

https://medium.com/flutter-community/advanced-flutter-matrix4-and-perspective-transformations-a79404a0d828

 

Advanced Flutter: Matrix4 And Perspective Transformations

Demystifying Matrix4 and utilising the full power of the Transform Widget

medium.com

https://medium.com/flutter/perspective-on-flutter-6f832f4d912e

 

Perspective on Flutter

Fun with 3D and the Transform widget

medium.com

 

 

상기내용을 기반으로 정리하였습니다.

모든 이미지들은 상기 링크에서 발췌해 왔습니다.

 

This content is based on above links. Also all images are from above links.

 

 

 

 

0. 개요

Transform 코드를 보다보면 matrix4 라는 것을 사용하여 이미지를 뒤틀거나 하는 예제를 많이 보았을 것 입니다.

이번 글에서는 이 matrix4 에 대해 이해해 봅시다.

 

기초지식을 이해 한 뒤에 아래 예제를 작성해 보겠습니다.

 

 

 

 

1. 기초지식

 

먼저 간단한 사실부터 짚고 넘어갑시다.

 

 

  • 플러터는 사실 3D로 렌더링 됩니다. 화면에 보이는것은 2D 지만요
  • 구글의 설명에 따르면 스마트폰의 GPU는 2D렌더링보다 3D렌더링에 더 최적화 되어있고 더 빠르다고 합니다.
  • Matrix4는 3차원 좌표를 3차원 다른 좌표로 투영시키는데 사용되는 matrix 입니다

 

"Pretty much all but the least powerful smartphones include amazingly fast GPUs, which are optimized for 3D graphics. That means that rendering 3D graphics is very fast. Consequently, almost everything you see on your phone is being rendered in 3D, even the 2D stuff. Crazy, huh?"

 

 

 

자 이제 플러터가 3D로 렌더링 되는것을 알았습니다.

 

그럼 플러터에서 x, y, z축이 어떤부분인지 알아야 겠지요?

 

 

 

바로 위 그림과 같습니다. 화면의 가로축이 x축이고 화면의 세로축이 y축입니다.

 

그럼 z축은 어디일까요? 바로 화면의 수직으로 올라와 있습니다.

 

 

 

2. 4d 매트릭스란?

고등학교때 배웠던 단위행렬입니다. 크기가 4x4인 단위행렬이지요

일단 이 사실만 알고 넘어갑시다.

 

 

 

그리고 아래의 코드로 사전 준비를 합시다.

import 'package:flutter/material.dart';


void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        body: Center(
          child: MyHomePage(),
        ),
      ),
    );
  }
}



class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {

  double x = 0;
  double y = 0;
  double z = 0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Transform(
          transform: Matrix4(
              1,0,0,0,
              0,1,0,0,
              0,0,1,0,
              0,0,0,1,
          )..rotateX(x)..rotateY(y)..rotateZ(z),
          alignment: FractionalOffset.center,
          child: GestureDetector(
            onPanUpdate: (details) {
              setState(() {
                y = y - details.delta.dx / 100;
                x = x + details.delta.dy / 100;
              });
            },
            child: Container(
              color: Colors.red,
              height: 200.0,
              width: 200.0,
            ),
          ),
        ),
      ),
    );
  }
}

 

 

위 코드를 실행하면 아래와 같이 빨간 사각형이 나오고 사각형을 드래그 해 보면 이리저리 회전을 합니다.

 

 

 

2. Scailing 하기

 

위 그림에서 x,y,z는 각각 x축방향, y축방향, z축방향의 스케일링을 의미합니다.

 

무슨말이냐고요?

 

가령 저기 x값을 1.5로 바꾸면 x축방향으로 위젯이 1.5배 늘어난다는 소리입니다!

 

한번 해보시면 아래처럼 됩니다. x축으로 사각형이 늘어났지요?

 

 

 

이번에는 y값을 2로 바꿔봅시다

 

세로로 쭈욱 늘어났습니다. 

 

 

z축을 scailing하면 어떨가요?

아쉽게도 화면에 보여지는건 x, y밖에 없기에 아무 일도 일어나지 않습니다.

 

 

 

 

 

어라 근데 맨 마지막의 1은 무엇인가요?

 

해당 1 부분은 x,y,z 모든 방향으로 scailing 해 주는 자리 입니다.

 

정확한 이해를 위해서는 대학 전공서재를 꺼내야겠지만 거기까지 하는건 너무 시간낭비니 결과만 알아둡시다.

 

저 SF 라는것은 딱봐도 Scailing Factor라는 의미겠지요? SF에 값을 2를 넣어봅시다. 즉 행렬 (3,3) 자리에 0.5를 넣는것이지요.

 

 

그럼 아래처럼 사각형의 넓이가 가로길이2배 세로길이2배니 4배가 됩니다!

 

 

 

 

3. Translate, 위젯 위치 옮기기

다음은 translate 입니다. translate라는 단어가 보통 사람들이 번역하다 라고 알고 있지만 기본적으로 무엇을 옮긴다는 뜻을 가지고 있습니다.

 

위에 x, y, z 값은 각각 x축 y축 z축 방향으로 위젯을 움직이겠다는 얘기 입니다.

 

 

자그럼 한번 x값에 75를 넣어볼까요?

 

즉 네모 위젯을 75픽셀만큼 x축으로 이동시키겠다는 뜻이겠죠!

 

보시는바와 같이 x축으로 네모가 이동하였습니다!

 

 

4. rotate, 위젯 회전시키기

 

 

위 예시는 각각 x축 기준, y축 기준, z축 기준으로 위젯을 회전시켜주는 값의 위치를 나타냅니다.

 

고등학교때 배웠던 회전행렬이랑 똑같네요!

 

허나 저기에 sine cosine 값 일일이 넣어주기 힘들겠지요?

 

그래서 아래처럼 코드를 이용하는것이 일반적입니다.

 

..rotateX(x)..rotateY(y)..rotateZ(z)
반응형

 

 

 

5. Perspective 위젯에 원근감 주기

 

 

다음 세개의 값을 이용하면 위젯에 원근감으 주실 수 있습니다.

 

 

 

자 위 예제에는 한가지 어색한점이 있습니다.

 

아래처럼 위젯을 x축방향으로 회전시키지만 우리 눈에서 멀어지는 부분이 작아지지 않지요?

 

 

 

아래 철도길 처럼 원근감을 주고싶은데 말이지요.

 

 

 

원근감을 주려면 바로 이 값을 이용하면 됩니다!

 

 

저 z값을 바꾸면 z축으로부터 멀어지는 부분은 작아지도록 원근감을 줄 수 있습니다.

 

값을 0.002를 넣어봅시다.

 

z축으로 멀어질 수록 철도레일처럼 크기가 작아지며 원근감이 생긴걸 보실수 있습니다.

 

 

 

자 그럼 나머지 x축 y축으로 원근감을 주면 어떻게 될까요?

 

먼저 x축에 원근감을 주어봅시다.

 

x축의 우측으로 갈 수록 위젯의 크기가 작아지고 왼쪽으로 갈 수록 위젯의 크기가 커지네요.

 

 

 

다음은 y축에 원금감을 주어봅시다.

 

 

y축의 값이 작아지면 위젯이 커지고 y축의 값이 커지면 위젯이 작아지네요!

 

 

 

 

 

6. SetEntry 함수

matrix4 를 공부하다 보면 setEntry라는 함수를 보실 수 있습니다. 아래처럼요

 

..setEntry(3, 2, 0.001)

이게 뭐하는놈이냐면 우리의 matrix4 행렬의 (3, 2) 자리에 값 0.001을 넣겠다 라는 뜻입니다.

 

즉 위 코드는 z축에 원근감 0.001을 넣게다는 의미이지요!

 

(그런데 보니깐 setEntry 함수에 row, col 위치가 바뀌어 있는거 같네요... z원근감은 2,3 이여야 할 텐데.. 구글에 물어봐야 할 것 같습니다)

 

 

 

 

7. 마무리

 

 

 

 

자 그럼 처음에 보여줬던 예제 있지요? 플러터 유튜브에 나오는 example

 

하기 코드로 작성할 수 있습니다. 한번 테스트해보고 배운내용 이해해 보시길 바랍니다.

 

// v2: add Gesture detector
import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Perspective',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key}) : super(key: key); // changed

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;
  Offset _offset = Offset.zero; // changed

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Transform(  // Transform widget
      transform: Matrix4.identity()
        ..setEntry(3, 2, 0.001) // perspective
        ..rotateX(0.01 * _offset.dy) // changed
        ..rotateY(-0.01 * _offset.dx), // changed
      alignment: FractionalOffset.center,
      child: GestureDetector( // new
        onPanUpdate: (details) => setState(() => _offset += details.delta),
        onDoubleTap: () => setState(() => _offset = Offset.zero),
        child: _defaultApp(context),
      )
    );
  }

  _defaultApp(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('The Matrix 3D'), // changed
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.display1,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
  }

}

 

반응형

댓글