Create a cube grid.
Support scaling, positioning, and rotating.
Work with transformation matrices.
Create simple camera projections.
[ 1. Visualizing Space ]
메시의 공간상의 변형(Translation, Rotation, Scale)은 정점에 의해서 다루어짐
우리는 3D Grid를 통해 이를 확인해 볼 예정임
Menu : GameObject -> Create Empty
Name : TransformationGrid
Script 생성
Name : TransformationGrid
using UnityEngine; public class TransformationGrid : MonoBehaviour { public Transform prefab; public int gridResolution = 10; Transform[] grid; void Awake () { grid = new Transform[gridResolution * gridResolution * gridResolution]; for (int i = 0, z = 0; z < gridResolution; z++) { for (int y = 0; y < gridResolution; y++) { for (int x = 0; x < gridResolution; x++, i++) { grid[i] = CreateGridPoint(x, y, z); } } } } }
점을 생성하는 프리 팹을 인스턴스화하고 좌표를 결정하며 고유 한 색을 지정
Transform CreateGridPoint (int x, int y, int z) { Transform point = Instantiate<Transform>(prefab); point.localPosition = GetCoordinates(x, y, z); point.GetComponent<MeshRenderer>().material.color = new Color( (float)x / gridResolution, (float)y / gridResolution, (float)z / gridResolution ); return point; }
회전과 스케일링과 같은 변형을 위해 그리드 큐브의 중간 점을 기준으로 할 수 있도록 한다.
Vector3 GetCoordinates (int x, int y, int z) { return new Vector3( x - (gridResolution - 1) * 0.5f, y - (gridResolution - 1) * 0.5f, z - (gridResolution - 1) * 0.5f ); }
Transformation.cs 드래그 >> (to) TransformationGrid(GameObject)
정점에서 사용할 프리팹으로 Cube 이용
Menu : GameObject >> 3D Object >> Create Cube
Scale(0.5, 0.5, 0.5)
Cube 프리펩 생성
Cube 드래그 >> (to) Project Window
Cube 프리팹 드래그 >> (to) TransformationGric : TransformationGrid(script) : prefab
[ 2. Transformation ]
Transformation 컴포넌트 만들기
여러가지 변형이 있을 수 있지만, 제한적으로 위치이동, 회전, 크기 조절만을 위한 컴퍼넌트 만들기
Transformation 클래스를 만들고, 변형 클래스들(PositionTransformation, ScaleTransformation, RotationTransformation)이 상속 받을 예정
Apply()라는 가상 함수를 만들어 각각의 역할을 담당하게 만들 예정
Script 생성 : Transformation.cs
using UnityEngine; public abstract class Transformation : MonoBehaviour { public abstract Vector3 Apply (Vector3 point); }
변형(translate, rotation, scale)들을 하나로 묶을 수 있도록(상속 받을 수 있도록) 기본 구성 요소를 만듭니다.
추상 클래스로 구현하고 Apply()함수는 구체적인 변환 구성 요소가 작업을 수행하는 데 사용되는 추상 메소드를 제공
TransforamtionGrid 수정
List를 사용하여 변형이 되는 요소(translate, rotation, scale)를 담을 수 있도록 함
using UnityEngine; using System.Collections.Generic; public class TransformationGrid : MonoBehaviour { … List<Transformation> transformations; void Awake () { … transformations = new List<Transformation>(); } }
void Update () { GetComponents<Transformation>(transformations); for (int i = 0, z = 0; z < gridResolution; z++) { for (int y = 0; y < gridResolution; y++) { for (int x = 0; x < gridResolution; x++, i++) { grid[i].localPosition = TransformPoint(x, y, z); } } } }
각 포인트를 변환하는 것은 원래 좌표를 가져온 다음 각 변환을 적용하여 수행
여기서는 매 프레임마다 변형을 축적하지 않고 수행하게 됨
Vector3 TransformPoint (int x, int y, int z) { Vector3 coordinates = GetCoordinates(x, y, z); for (int i = 0; i < transformations.Count; i++) { coordinates = transformations[i].Apply(coordinates); } return coordinates; }
Transformation로컬 오프셋으로 사용할 위치를 확장하는 새로운 구성 요소(Translation)를 만듬
Script 생성 : PositionTransformation.cs
using UnityEngine; public class PositionTransformation : Transformation { public Vector3 position; // Apply 가상함수를 재정의 public override Vector3 Apply (Vector3 point) { return point + position; } }
PositionTransformation.cs 드래그 >> (to) TransformationGrid(Object)
[ 2.2 Scaling ]
Scaling 요소 만들기
Script 생성 : ScaleTransformation.cs
using UnityEngine; public class ScaleTransformation : Transformation { public Vector3 scale; public override Vector3 Apply (Vector3 point) { point.x *= scale.x; point.y *= scale.y; point.z *= scale.z; return point; } }
PositionTransformation.cs 드래그 >> (to) TransformationGrid(Object)
Scale Transformation과 Position Transformation 위치 바꾸기
포지셔닝과 스케일링을 동시에 시도하는 경우 원하지 않는 출력이 생성(Position이 진행되고, Scale이 진행되면 원점으로 부터의 상대 거리만큼을 Scale 하기 때문에 문제가 생김)
컴포넌트의 구성 요소의 순서를 변경함으로써 이를 해결
Rotation 요소 만들기
Script 생성 : RotationTransformation.cs
우선은 단순하게 z축을 기준으로만 회전할 수 있도록 하자.
using UnityEngine; public class RotationTransformation : Transformation { public Vector3 rotation; public override Vector3 Apply (Vector3 point) { // Mathf.Deg2Rad : Degree를 Radian으로 바꿔줌 float radZ = rotation.z * Mathf.Deg2Rad; float sinZ = Mathf.Sin(radZ); float cosZ = Mathf.Cos(radZ); return new Vector3( point.x * cosZ - point.y * sinZ, point.x * sinZ + point.y * cosZ, point.z ); } }
Unity는 왼손 좌표계를 사용하므로 반 시계 방향으로 이동
삼각함수를 이용한 회전을 위해 Degree를 Radian으로 변환하고 이를통해 회전 결과를 얻음
RotationTransformation.cs 드래그 >> (to) TransformationGrid(Object)
Rotation Transformation도 Position Transformation보다 위쪽에 위치할 수 있도록 함
3개의 축(x,y,z) 회전 구현
[ 3.1 Matrices ]
Matrices를 사용하는 이유
여러 개의 정점을 이동, 회전, 크기 조정을 하는데 있어서 정점마다 이동, 회전, 크기를 따로 계산 하기 보다는 이동, 회전, 크기 조정을 하는 계산 식을 하나의 표현식으로 만들어서 이것을 정점마다 대입하는 것이 빠름
[ 3.2 3D Rotation Matrices ]
3 by 3 회전 Matrices
[ 3.3 Rotation Matrices for X and Y ]
[ 3.4 Unified Rotation Matrix ]
3개의 축을 적용한 회전 코드를 RotationTransformation.cs에 추가
public override Vector3 Apply (Vector3 point) { float radX = rotation.x * Mathf.Deg2Rad; float radY = rotation.y * Mathf.Deg2Rad; float radZ = rotation.z * Mathf.Deg2Rad; float sinX = Mathf.Sin(radX); float cosX = Mathf.Cos(radX); float sinY = Mathf.Sin(radY); float cosY = Mathf.Cos(radY); float sinZ = Mathf.Sin(radZ); float cosZ = Mathf.Cos(radZ); Vector3 xAxis = new Vector3( cosY * cosZ, cosX * sinZ + sinX * sinY * cosZ, sinX * sinZ - cosX * sinY * cosZ ); Vector3 yAxis = new Vector3( -cosY * sinZ, cosX * cosZ - sinX * sinY * sinZ, sinX * cosZ + cosX * sinY * sinZ ); Vector3 zAxis = new Vector3( sinY, -sinX * cosY, cosX * cosY ); return xAxis * point.x + yAxis * point.y + zAxis * point.z; }
[4.1 Homogeneous Coordinates ]
[ 4.2 Using Matrices ]
Unity의 Matrix4x4구조체
행렬의 곱셈을 수행 할 수 있음
Transformation.cs
public abstract Matrix4x4 Matrix { get ; }
Transformation변환 행렬을 검색 하기 위해 추상 readonly 특성만 추가
Apply()는 더 이상 추상적 일 필요가 없음 (단지 행렬을 잡고 곱셈을 수행)
public Vector3 Apply (Vector3 point) { //Matrix.MultiplyPoint : 행렬에 point를 곱하고 있다. return Matrix.MultiplyPoint(point); }
PositionTransformation.cs
public override Matrix4x4 Matrix { get { Matrix4x4 matrix = new Matrix4x4(); // SetRow : 행렬을 줄 단위로 채우는데 편리한 함수 matrix.SetRow(0, new Vector4(1f, 0f, 0f, position.x)); matrix.SetRow(1, new Vector4(0f, 1f, 0f, position.y)); matrix.SetRow(2, new Vector4(0f, 0f, 1f, position.z)); matrix.SetRow(3, new Vector4(0f, 0f, 0f, 1f)); return matrix; } }
ScaleTransformation.cs
public override Matrix4x4 Matrix { get { Matrix4x4 matrix = new Matrix4x4(); matrix.SetRow(0, new Vector4(scale.x, 0f, 0f, 0f)); matrix.SetRow(1, new Vector4(0f, scale.y, 0f, 0f)); matrix.SetRow(2, new Vector4(0f, 0f, scale.z, 0f)); matrix.SetRow(3, new Vector4(0f, 0f, 0f, 1f)); return matrix; } }
RotationTransformation.cs
public override Matrix4x4 Matrix { get { float radX = rotation.x * Mathf.Deg2Rad; float radY = rotation.y * Mathf.Deg2Rad; float radZ = rotation.z * Mathf.Deg2Rad; float sinX = Mathf.Sin(radX); float cosX = Mathf.Cos(radX); float sinY = Mathf.Sin(radY); float cosY = Mathf.Cos(radY); float sinZ = Mathf.Sin(radZ); float cosZ = Mathf.Cos(radZ); Matrix4x4 matrix = new Matrix4x4(); matrix.SetColumn(0, new Vector4( cosY * cosZ, cosX * sinZ + sinX * sinY * cosZ, sinX * sinZ - cosX * sinY * cosZ, 0f )); matrix.SetColumn(1, new Vector4( -cosY * sinZ, cosX * cosZ - sinX * sinY * sinZ, sinX * cosZ + cosX * sinY * sinZ, 0f )); matrix.SetColumn(2, new Vector4( sinY, -sinX * cosY, cosX * cosY, 0f )); matrix.SetColumn(3, new Vector4(0f, 0f, 0f, 1f)); return matrix; } }
변환 행렬(Position Transformation, Scale Transformation, Rotation Transformation)들을 하나의 행렬로 결합
TransformationGrid.cs
Matrix4x4 transformation;
Update()마다 변환 행렬을 수행
void Update () { UpdateTransformation(); for (int i = 0, z = 0; z < gridResolution; z++) { … } } void UpdateTransformation () { GetComponents<Transformation>(transformations); if (transformations.Count > 0) { transformation = transformations[0].Matrix; for (int i = 1; i < transformations.Count; i++) { transformation = transformations[i].Matrix * transformation; } } }
첫 번째 행렬을 잡아서 다른 행렬과 곱하는 과정이 포함
Vector3 TransformPoint (int x, int y, int z) { Vector3 coordinates = GetCoordinates(x, y, z); return transformation.MultiplyPoint(coordinates); }
이제 더 이상 정점별로 변환들(Translation, Rotation, Scale)을 호출하지 않고 가지고 있는 변환 최종 행렬로만 수행
이 새로운 접근법은 모든 점마다 개별적으로 각 변환 행렬을 작성하고 개별적으로 적용하고 있기 때문에 더 효율적
3D 상의 위치에서 2D 공간의 위치로 변환이 필요하며, 이를 위한 변환 행렬을 투영 행렭이라고 함
스크립트 생성 : CameraTransformation.cs
using UnityEngine; public class CameraTransformation : Transformation { public override Matrix4x4 Matrix { get { Matrix4x4 matrix = new Matrix4x4(); matrix.SetRow(0, new Vector4(1f, 0f, 0f, 0f)); matrix.SetRow(1, new Vector4(0f, 1f, 0f, 0f)); matrix.SetRow(2, new Vector4(0f, 0f, 1f, 0f)); matrix.SetRow(3, new Vector4(0f, 0f, 0f, 1f)); return matrix; } } }
우선은 단위 벡터로 시작하고 있음
최종적으로 수행될 수 있도록 컴포넌트 마지막에 배치
우리의 원시 카메라는 원점에 위치하여 양의 Z 방향으로 투영
카메라를 움직이는 것은 세계를 반대 방향으로 움직이는 것과 같은 효과
Unity는 행렬 역변환을 사용하여 세계를 돌리는 형태로 동일한 작업을 수행
멀리있는 것들은 작게 가까이 있는 것은 크게 보이게 하는 것으로 카메라로부터의 거리에 따라 포인트를 스케일링하여이 효과를 재연 할 수 있음
Z 좌표(카메라와의 거리)에 따라 x, y 좌표가 설정될 수 있도록 연계식을 만들기 위해 모든 것을 Z 좌표로 나눔
z 값이 작을 수록 상대적으로 x, y값은 커짐
행렬 곱셈으로 이를 표현할 수 있음으로 할 수 있음
항등 행렬의 맨 아래 행을 [
]으로 변경하면
0
,
0
,
1
,
0
[0,0,1,0]. 그러면 결과의 네 번째 좌표가 원래 Z 좌표와 동일하게됨
원점과 투영 기준면 간의 거리도 투영에 영향을 줌
그것은 카메라의 초점 길이와 같은 역할을합니다. 더 크게 만들수록 시야가 좁아집니다. 지금 우리는 1의 초점 길이를 사용하고 있는데, 이는 90 °의 시야를 만들어냅니다. 우리는이를 구성 가능하게 만들 수 있습니다.
CameraTransformation.cs
public float focalLength = 1f ;