Q. 3D Graphics에서 왜 행렬로 표현하나요?
이 질문에 답이 Matrix4를 배우는 이유가 된다.
3차원 공간에서의 '크기', '이동', '회전' 정보를 한 번에 표현할 수 있는 방법은 "행렬"이다.
여러 행렬이 있지만 위 정보를 담은 행렬을 "변환행렬(Transformation Matrix = TM)"이라고 칭한다.
행렬이 세상에 존재하지 않는다고 가정한다면, x, y, z 축에 대한 크기, 이동, 회전 정보를 담을 방법이 있을까?
아주 긴 식으로 표현할라고하면 할 수 있다. 하지만 이것을 행렬은 한 번에 표현할 수 있다. 그래서 그래픽스에서 행렬을 사용하여 그래픽을 수식으로 표현한다.
그러므로, 3D 애니메이션을 잘 구현하고자 한다면 Matrix4를 알아야 한다.
가운데 보면 Sx, Sy, Sx 가 있는 4x4 행렬이 있다. 이것을 "스케일 행렬"이라고 부른다.
Sx
: x의 크기에 배율을 결정할 계수Sy
: y의 크기에 배율을 결정할 계수Sz
: z의 크기에 배율을 결정할 계수맨 아래 x, y, z 라는 값이 있다. 해당 축의 방향으로 offset된다고 이해하면 쉽다.
회전행렬은 이전과는 다르게 계산이 좀 더 요구된다. 이유는 3D 에서 회전은 x, y, z 축에 더해 회전량이라는 것이 추가된다. 이 회전량이라는 단위는 축별로 연산을 해야한다. 간단하게 하나의 축이 추가된 것이 아니라 3 개의 연산을 해야 한번의 회전을 온전히 행렬로 표현할 수 있다.
X축 회전 행렬
Y축 회전 행렬
Z축 회전 행렬
변환 행렬로 하고 싶은 것은 딱 3 개이다.
크기와 이동은 행렬에 적당히 값을 넣으면 될 것 같다. 그런데 회전은 값을 넣기가 빡세다. 그래서 Flutter에서는 이 변환행렬을 만들기 쉽게 method들을 구현해두었다.
Matrix4(
1,0,0,0,
0,1,0,0,
0,0,1,0,
0,0,0,1,
);
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,
),
),
],
),
),
),
);
}
}
크기를 늘리기 위해서는 아까, 단위행렬에 해당하는 위치에 계수를 부여하면 된다.
여기에서 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는 적용이 쉽도록 되어 있다. 앞으로 있는 다양한 애니메이션을 통해서 실습을 본격적으로 해볼 예정이다.
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축으로의 회전은 어떻게 동작하는 것인지 아실까요?