[Three.js] 03. Matrix Transformation

juuns·2024년 2월 5일
0

Three.js

목록 보기
4/5
post-thumbnail

기본적으로 transformation이란 꼭짓점의 위치를 변경하는 것으로, translation, scaling, rotation이 있다.

“Transformation은 행렬이다.”

1. Object Transformation


Three.js는 행렬을 사용해 3D 변형(translation, scaling, rotation)을 한다. Object3D의 모든 인스턴스는 matrix를 가지고 있어, 오브젝트의 위치, 회전, 확대 정보를 담고 있다.

Three.js의 오브젝트 transformation에는 두 가지 방법이 있다.

1.1. matrixAutoUpdate = true


1.1.1. Object3D의 속성 변경

객체의 position, quaternion, scale 속성을 변경하면 three.js는 이 3개의 속성을 이용해 객체의 matrix 속성을 다시 계산한다. 예를 들어, 다음과 같이 사용할 수 있다.

box1.position.set(2, 3, 1); // x: 2, y: 3, z: 1로 이동
box1.position.x = -2; 

box2.rotation.set(Math.PI/4, 0, Math.PI/2); // x축으로 PI/4만큼, z축으로 PI/2만큼 회전
box2.rotation.y += 0.01;

box3.scale.set(2, 0.5, 1); // x축으로 2배, y축으로 0.5배, z축으로 1배 크기 조절

matrixAutoUpdate=true 가 기본값이고, 이 값을 설정하면 position, rotation or quarternion, scale을 매 프레임 자동으로 계산하며, matrixWorld 속성 역시 재계산한다.

1.1.2. 예제

예제 전체 코드 : 서로 다른 속도로 회전하는 박스 3개

const h_scr = window.innerWidth;
const v_scr = window.innerHeight; 
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(45, h_scr/v_scr, 0.1, 1000);
camera.position.z = 5;
    
const renderer = new THREE.WebGLRenderer({canvas: HelloCanvas}); 
renderer.setSize( h_scr, v_scr ); 
    
const geometry = new THREE.BoxGeometry();
const material1 = new THREE.MeshPhongMaterial( { 
		color: 0xff0000, shininess : 90.0 });
const material2 = new THREE.MeshPhongMaterial( { 
		color: 0x00ff00, shininess : 90.0 });
const material3 = new THREE.MeshPhongMaterial( { 
		color: 0x0000ff, shininess : 90.0 });
    
const light1 = new THREE.DirectionalLight(0xffffff, 1.0); 
light1.position.set (2,2,2); 
scene.add( light1 ); 
const light2 = new THREE.AmbientLight(0x303030);  
scene.add( light2 ); 
    
const box1 = new THREE.Mesh( geometry, material1 );
const box2 = new THREE.Mesh( geometry, material2 );
const box3 = new THREE.Mesh( geometry, material3 );
    
scene.add(box1); 
scene.add(box2); 
scene.add(box3); 

box2.position.x = -2; 
box3.position.x = 2; 
var rot = 0.0; 
    
const animate = function () {
	requestAnimationFrame( animate );
    
   	rot += 0.01;
   	box1.rotation.y = rot;
   	box2.rotation.y = rot;
   	box3.rotation.y = rot;
    
   	renderer.render( scene, camera );
};
animate();

예제 코드 중 아래 부분을 보자. Object3D의 속성을 이용해 행렬을 변환하는 방법이다.

  • 우선 box 3개를 scene에 추가하고
  • box2는 왼쪽으로 2만큼, box3은 오른쪽으로 2만큼 이동시킨다.
  • 이후 1/60초에 한 번씩 렌더링되는 animate 함수에서 박스 3개의 rotation-y값을 업데이트 해준다.
scene.add(box1); 
scene.add(box2); 
scene.add(box3); 

box2.position.x = -2; 
box3.position.x = 2; 
var rot = 0.0; 

const animate = function () {
	requestAnimationFrame( animate );

	rot += 0.01;
	box1.rotation.y = rot;
	box2.rotation.y = rot;
	box3.rotation.y = rot;

	renderer.render( scene, camera );
};
animate();

1.2. matrixAutoUpdate = false


1.2.1. matrix 수동 업데이트

matrixAutoUpdate = false 로 설정하면 transformation을 수동으로 업데이트할 수 있다. 행렬이 자동으로 업데이트되지 않기 때문에 직접 매트릭스를 설정해야 한다. 주로 성능 향상을 위해 이 방법을 사용하기도 한다.

const cube = new THREE.Mesh(geometry, material);
cube.matrixAutoUpdate = false; // 수동 업데이트로 설정

// 변환 수동 업데이트
cube.position.set(2, 3, 1);
cube.rotation.set(Math.PI / 4, 0, Math.PI / 2);
cube.scale.set(2, 0.5, 1);

// 매트릭스를 직접 설정
cube.updateMatrix();

위처럼 속성값을 변경한 후에 수동 업데이트를 해줄 수도 있고, 행렬을 직접 만들고 직접 곱해줄 수도 있다. 후자의 경우는 아래 예제를 통해 살펴보겠다.

2.2. 예제

1.1.2. 예제와 달라진 부분만 가져온 코드이다.

우선 box 3개를 scene에 추가하는 것까지는 동일하다.

scene.add(box1); 
scene.add(box2); 
scene.add(box3); 

이후 변환을 위한 행렬 3개 rM, tM1, tM2를 만든다. Matrix4는 three.js가 제공하는 객체로 4x4 행렬을 의미한다.

var rM = new THREE.Matrix4; 
var tM1 = new THREE.Matrix4; 
var tM2 = new THREE.Matrix4; 

makeTranslation()Matrix4 클래스에 속한 메서드로, 주어진 x, y, z 좌표만큼 이동하는 translation matrix를 생성한다. 4x4 변환 행렬 중 translation에 해당하는 마지막 열 값을 파라미터로 가진다고 생각하면 된다.

따라서 tM1 은 x축으로 2만큼 이동시키는 행렬, tM2 는 x축으로 -2만큼 이동시키는 행렬이 된다.

rot는 회전 각도를 나타낸다.

tM1.makeTranslation(2,0,0);
tM2.makeTranslation(-2,0,0);
var rot = 0.0; 

모든 메쉬의 matrixAutoUpdate 속성을 false 로 설정해준다.

box1.matrixAutoUpdate = false; 
box2.matrixAutoUpdate = false; 
box3.matrixAutoUpdate = false;

이제 렌더링하는 함수에서 우선 메쉬들의 행렬을 identity matrix로 초기화한 후, 변환하고 싶은 만큼 변환 행렬들을 곱해준다. 이때, 코드 상에서 변환행렬을 곱한 순서와 반대로 곱해진다. 예를 들어, box2에 tM1rM 순서로 곱했지만 실제 행렬 계산에서는 rMtM1 순서대로 곱해진다. 즉, 회전을 먼저 시킨 후에 이동을 하게 만드는 것이다. 따라서 이 예제에서는 1.1.2.예제 와 동일하게 3개의 박스가 나란히 서서 자전하게 된다.

const animate = function () {
	requestAnimationFrame( animate );

	box1.matrix.identity(); 
	box2.matrix.identity(); 
	box3.matrix.identity(); 

	rot += 0.01;
	rM.makeRotationY(rot);

	box1.matrix.multiply(rM); 

	box2.matrix.multiply(tM1); 
	box2.matrix.multiply(rM); 

	box3.matrix.multiply(tM2); 
	box3.matrix.multiply(rM); 

	renderer.render( scene, camera );
};
animate();

2. Hierarchical Transformation (계층적 변환)


2.1. 계층적 변환이란?

계층적 변환은 객체 간의 부모-자식 관계를 이용하여 객체를 그룹화하고, 그룹의 변환을 부모와 자식에게 동시에 적용하는 방식을 의미한다. 객체들 간의 계층 구조를 형성해, 부모 객체의 변환을 자식 객체에게 상속하는 효과를 얻을 수 있다.

아래 그림처럼 Mesh들 간의 계층 구조를 만들 수도 있고, 카메라나 조명 등에도 계층 구조를 적용할 수 있다.

2.2. local matrix와 world matrix

three.js의 모든 Object3D 객체는 matrixmatrixWorld라는 속성을 가진다. 둘 다 4x4 행렬이며 matrix는 객체의 local coordinate에서의 변환 정보를 담은 행렬이고, matrixWorld는 world coordinate의 변환 정보를 담은 행렬로 부모 객체의 변환까지 반영된 값을 가진다.

아래 그림에서 보면, Box1에 해당하는 Mesh는 Scene에게 전달받은 Scene의 world matrix와 자신의 local matrix를 곱해서 world matrix를 만든다. 이후, box1 은 자신의 world matrix를 자식 mesh에게 전달해주고, 자식 mesh는 box1의 world matrix와 자신의 local matrix를 곱해서 world matrix를 만든다. 이런 식으로 계층 구조를 만들어낼 수 있다.

2.3. 예제

위에서 만들었던 3개의 상자에 계층 구조를 부여하고, 각 상자에 회전 및 이동 등의 변환을 만들어 계층 구조가 어떻게 적용되는지 확인해보자. 이 예제도 1.1.2. 예제 와 달라진 부분만 살펴보겠다.

여기가 바로 계층 구조를 형성하는 부분이다. 원래는 3개의 box를 모두 Scene에 추가했던 것과 달리 여기서는 box1Scene에 추가하고, box2box1에, box3box2에 추가했다. 따라서 Scene - box1 - box2 - box3의 계층 구조를 가지게 된다.

scene.add(box1); 
box1.add(box2);   // Make Hierachy
box2.add(box3);   // Make Hierachy

transformation은 아래와 같이 automatic transformation으로 진행한다. box2box3의 위치를 아래와 같이 설정하면, box2box1을 기준으로 x축으로 -2만큼 이동, box3box2를 기준으로 2만큼 이동하게 된다.

box2.position.x = -2; 
box3.position.x = 2; 

scaling도 마찬가지로, box2box1의 크기를 기준으로 모든 축에 대해 0.5배 한 것이고, box3box2의 크기를 기준으로 모든 축에 대해 0.5배 한 것이다. 따라서 box2box1의 1/8이고, box3box1의 1/64이 된다.

box2.scale.set(0.5,0.5,0.5); 
box3.scale.set(0.5,0.5,0.5); 

box1을 x축의 방향으로 아래와 같이 회전하면 box2box3도 같은 만큼 회전된다.

var rot = 0.0; 

box1.rotation.x = 60 * 3.14 / 180; 

렌더링 함수에서 모든 박스의 rotation-y 값을 업데이트 해준다. 이때 rotation-y가 계속 변화하므로 모든 박스는 자전을 하는데 box1-box2-box3 순으로 빠르게 자전한다. 또한 계층 구조에 의한 상대적인 position을 갖는데 이는 회전과 조합되어 box2box1 주위를, box3box2 주위를 공전하는 애니메이션이 만들어진다.

const animate = function () {
	requestAnimationFrame( animate );

	rot += 0.01;
	box1.rotation.y = rot;
	box2.rotation.y = rot*2;
	box3.rotation.y = rot*4;

	renderer.render( scene, camera );
};
animate();

참고: [SWTT] Three.js 튜토리얼

0개의 댓글