Flutter, Animation, Matrix4

Uno·2024년 1월 16일
1

flutter

목록 보기
13/16

Matrix4를 배우는 이유

Q. 3D Graphics에서 왜 행렬로 표현하나요?

이 질문에 답이 Matrix4를 배우는 이유가 된다.

3차원 공간에서의 '크기', '이동', '회전' 정보를 한 번에 표현할 수 있는 방법은 "행렬"이다.
여러 행렬이 있지만 위 정보를 담은 행렬을 "변환행렬(Transformation Matrix = TM)"이라고 칭한다.

Matrix4=(a1,1a1,2a1,3a1,4a2,1a2,2a2,3a2,4a3,1a3,2a3,3a3,4a4,1a4,2a4,3a4,4)Matrix4 = \begin{pmatrix} a_{1,1} & a_{1,2} & a_{1,3} & a_{1,4} \\ a_{2,1} & a_{2,2} & a_{2,3} & a_{2,4} \\ a_{3,1} & a_{3,2} & a_{3,3} & a_{3,4} \\ a_{4,1} & a_{4,2} & a_{4,3} & a_{4,4} \end{pmatrix}

행렬이 세상에 존재하지 않는다고 가정한다면, x, y, z 축에 대한 크기, 이동, 회전 정보를 담을 방법이 있을까?
아주 긴 식으로 표현할라고하면 할 수 있다. 하지만 이것을 행렬은 한 번에 표현할 수 있다. 그래서 그래픽스에서 행렬을 사용하여 그래픽을 수식으로 표현한다.

그러므로, 3D 애니메이션을 잘 구현하고자 한다면 Matrix4를 알아야 한다.

변환 벡터

Scaling

가운데 보면 Sx, Sy, Sx 가 있는 4x4 행렬이 있다. 이것을 "스케일 행렬"이라고 부른다.

(XYZ1)=(Sx0000Sy0000Sz00001)(XYZ1)\begin{pmatrix} X' \\ Y' \\ Z' \\ 1 \end{pmatrix} = \begin{pmatrix} S_x & 0 & 0 & 0 \\ 0 & S_y & 0 & 0 \\ 0 & 0 & S_z & 0 \\ 0 & 0 & 0 & 1 \end{pmatrix} * \begin{pmatrix} X \\ Y \\ Z \\ 1 \end{pmatrix}
  • Sx: x의 크기에 배율을 결정할 계수
  • Sy: y의 크기에 배율을 결정할 계수
  • Sz: z의 크기에 배율을 결정할 계수

Translate

맨 아래 x, y, z 라는 값이 있다. 해당 축의 방향으로 offset된다고 이해하면 쉽다.

[100001000010xyz1]\begin{bmatrix} 1 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 \\ 0 & 0 & 1 & 0 \\ x & y & z & 1 \end{bmatrix}
  • x: x축을 기준으로 양의 방향으로 이동한다.
  • y: y축을 기준으로 양의 방향으로 이동한다.
  • z: z축을 기준으로 양의 뱡향으로 이동한다.

Rotation

회전행렬은 이전과는 다르게 계산이 좀 더 요구된다. 이유는 3D 에서 회전은 x, y, z 축에 더해 회전량이라는 것이 추가된다. 이 회전량이라는 단위는 축별로 연산을 해야한다. 간단하게 하나의 축이 추가된 것이 아니라 3 개의 연산을 해야 한번의 회전을 온전히 행렬로 표현할 수 있다.

X축 회전 행렬

[10000cos(x)sin(x)00sin(x)cos(x)00001]\begin{bmatrix} 1 & 0 & 0 & 0 \\ 0 & \cos(x) & \sin(x) & 0 \\ 0 & -\sin(x) & \cos(x) & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix}

Y축 회전 행렬

[cos(x)0sin(x)00100sin(x)0cos(x)00001]\begin{bmatrix} \cos(x) & 0 & -\sin(x) & 0 \\ 0 & 1 & 0 & 0 \\ \sin(x) & 0 & \cos(x) & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix}

Z축 회전 행렬

[cos(x)sin(x)00sin(x)cos(x)0000100001]\begin{bmatrix} \cos(x) & \sin(x) & 0 & 0 \\ -\sin(x) & \cos(x) & 0 & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix}

Flutter 에서 변환행렬 사용하기

변환 행렬로 하고 싶은 것은 딱 3 개이다.

  • 크기
  • 이동
  • 회전

크기와 이동은 행렬에 적당히 값을 넣으면 될 것 같다. 그런데 회전은 값을 넣기가 빡세다. 그래서 Flutter에서는 이 변환행렬을 만들기 쉽게 method들을 구현해두었다.

Matrix4(  
  1,0,0,0,  
  0,1,0,0,  
  0,0,1,0,  
  0,0,0,1,  
);
  • 이 코드는 Flutter에 제공하는 4x4 행렬을 표현하는 방법이다. 그런데 이렇게 생긴 행렬을 "단위 행렬" 이라고 부른다.
  • 매번 이렇게 작성하기 귀찮다면, 아래 방식으로 하자.
Matrix4.identity();

그리고 초기 시작 프로젝트 상태는 아래 코드를 실행한다.

import 'package:flutter/material.dart';  
  
void main() => runApp(MyApp());  
  
class MyApp extends StatelessWidget {  
    
  Widget build(BuildContext context) {  
    return MaterialApp(  
      debugShowCheckedModeBanner: false,  
      home: Scaffold(  
        body: Center(  
          child: MyHomePage(),  
        ),  
      ),  
    );  
  }  
}  
  
class MyHomePage extends StatefulWidget {  
    
  _MyHomePageState createState() => _MyHomePageState();  
}  
  
class _MyHomePageState extends State<MyHomePage> {  
  double x = 0;  
  double y = 0;  
  double z = 0;  
  
    
  Widget build(BuildContext context) {  
    return Scaffold(  
      body: Center(  
        child: Transform(  
          transform: Matrix4.identity(),  
          alignment: FractionalOffset.center,  
          child: Stack(  
            children: [  
              Container(  
                color: Colors.deepPurpleAccent,  
                height: 200.0,  
                width: 200.0,  
              ),  
              Positioned(  
                top: 10,  
                left: 90,  
                child: CircleAvatar(  
                  radius: 10.0,  
                  backgroundColor: Colors.red,  
                ),  
              ),  
            ],  
          ),  
        ),  
      ),  
    );  
  }  
}

크기 늘리기

크기를 늘리기 위해서는 아까, 단위행렬에 해당하는 위치에 계수를 부여하면 된다.

(XYZ1)=(Sx0000Sy0000Sz00001)(XYZ1)\begin{pmatrix} X' \\ Y' \\ Z' \\ 1 \end{pmatrix} = \begin{pmatrix} S_x & 0 & 0 & 0 \\ 0 & S_y & 0 & 0 \\ 0 & 0 & S_z & 0 \\ 0 & 0 & 0 & 1 \end{pmatrix} * \begin{pmatrix} X \\ Y \\ Z \\ 1 \end{pmatrix}

여기에서 Sx, Sy, Sz에 값을 넣어주면된다.

Flutter는 이에 대해 매서드를 구현해두었고, 쉽게 추가할 수 있다. 이제 이 위치를 안외워도 된다.
메서드는 void scale(dynamic x, [double? y, double? z]) 이다.

transform: Matrix4.identity()  
	..scale(1.5, 2.0, 2.5),

x축으로 1.5배
y축으로 2.0배
z축으로 2.5배
만큼 늘어났다.
~~참고로 z축은 알 수가 없다. 애초에 크기에 대한 값이 0 이므로, 아무리 곱해도 변화가 없다. ~~

회전시키기

현재 코드에 회전을 추가한다.
아래 코드를 StackWidget 부모위젯으로 추가한다.

GestureDetector(
	child: Stack(...)
	onPanUpdate: (details) {
		setState(() {
			y = y - details.delta.dx / 100;
			x = x + details.delta.dy / 100;
		})
	}
)

그리고 Transform Widget에는 transform 파라미터를 다음과 같이 수정한다.

Transform(
	transform: Matrix4.identity()
		..scale(1.5, 2.0, 100) // 여기는 마음대로 입력해도됨
		..rotate(x)
		..rotate(y)
		..rotate(z),
	alignment: FractionalOffset.center,
	child: GestureDetector(...)
	)
	...
)

이렇게 하면 클릭하고 드래그하게되면, x 축으로든 y축으로든 회전이 적용된다.

Matrix4.identity()
    ..scale(1.5, 2.0, 100)
    ..rotateX(x)
    ..rotateY(y)
    ..rotateZ(z)

이 코드를 보면 rotateX, rotateY, rotateZ 메서드가 있다. 여기에 값을 추가하면, 위에서 배운 변환행렬의 Rotation에 적절한 값이 입력된다. 여기에 값을 전달하면 Radian 단위로 입력된다. 즉, 회전량이 나온다는 뜻이다.

alignment: FractionalOffset.center,

이 코드는 어디를 축으로 회전하는지 결정하는 파라미터를 나타낸다.

onPanUpdate: (details) {
  setState(() {
    y = y - details.delta.dx / 100;
    x = x + details.delta.dy / 100;
  });
}

마지막으로 onPanUpdate 내부 코드이다. onPanUpdate 메서드는 드래그할 때, 지속적으로 호출되는 이벤트 핸들링 메서드이다.

그 안에 코드로 x, y 값을 결정하고 있다.

y = y - details.delta.dx / 100;

이 코드는 y 값을 결정하는데 dx값에 영향을 받고 있다. 여기서 y 값은 rotateY() 메서드에 전달될 것이다. rotateY의 파라미터로 값이 전달되면 이것은 Radian으로 변환된다.

즉, y 값은 회전각도에 영향을 준다. 우리가 y축으로 회전을 원한다면, x축으로 드래그를할 것이다. 그리고 현재의 y 값은 회전된 량을 나타낸다.

y: 회전되어진 각
dx: 가로로 스크롤된 거리

이와 같은 논리로 x 값도 설명된다.

x = x + details.delta.dy / 100;

이동하기

이동하기는 Offset이다.

Matrix4.identity()
  ...
  ..translate(30.0, 100.0, 0.0)

설명할 것도 없다. 원하는 Offset 만큼 값을 넣으면 이동한다.

원근감 주기

행렬을 설명할 때, 설명하지 않은 부분인데, (3,2)위치에 값을 넣어주면 원근감이 부여된다.

0 ~ 1 사이의 값을 넣는다. 0 에 가까울수록 원근감이 적용 범윅 커진다.

Matrix4.identity()  
	...
	..setEntry(3, 2, 0.001),

정리

Matrix라는 게 순간적으로 어지러움을 유발할 수 있다. 하지만, 잘 생각해보면, 이것보다 편한 기입방법은 고안하기가 더 어려운 것 같다.

어째든 Flutter는 적용이 쉽도록 되어 있다. 앞으로 있는 다양한 애니메이션을 통해서 실습을 본격적으로 해볼 예정이다.

참고자료

profile
iOS & Flutter

1개의 댓글

comment-user-thumbnail
2024년 4월 1일

translate가
1000
0100
0010
xyz1
인데

Matrix4.identity()
...
..translate(30.0, 100.0, 0.0)
이 결과를 찍어보면
[0] 1.0,0.0,0.0,30.0
[1] 0.0,1.0,0.0,100.0
[2] 0.0,0.0,1.0,0.0
[3] 0.0,0.0,0.002,1.0
이렇게 나오는데, 혹시 왜 그런지 아시나요?

그리고
..rotateZ(z)
여기서 z 값을 변화 시켜주는 부분이 없어서
지우고 해보니 동작하는 것 같은데, z축으로의 회전은 어떻게 동작하는 것인지 아실까요?

답글 달기