본문 바로가기

Unity/3D

Unity C# Tutorials - Mesh Deformation(Making a Stress Ball) 번역본

반응형

원본 : https://catlikecoding.com/unity/tutorials/mesh-deformation/

 

Mesh Deformation, a Unity C# Tutorial

A Unity C# scripting tutorial in which you will deform a mesh, turning it into a stress ball.

catlikecoding.com

참고 : 이 글은 어디까지나 원문을 부족한 영어실력으로 최대한 읽을 수 있게 번역만 해둔 것 입니다. 수정사항이나 이해가 안되시는 부분은 댓글로 부탁드립니다.

 

Mesh Deformation

 

Making a Stress Ball

  • 광선을 시각화 하고 객체를 맞춥니다.
  • 적용될 힘을 정점들을 위한 속도(Velocity)로 변환합니다.
  • 스프링과 댐핑을 이용하여 형태를 유지합니다.
  • 객체의 변형 후 원복합니다.

 

이 튜토리얼은 메쉬 변형에 대한 소개입니다. 이 자습서에선 메쉬를 탄력있는 덩어리로 바꿔 놓을 것 입니다.

 

이 자습서는 Cube Sphere를 따르며, 해당 자습서에서 생성된 메쉬 조작을 이어서 하는 것 입니다. Unity 5.0.1 이상에서 작성되었습니다.

 

압력을 가한 모습

 

1. Scene 설정

중심에 단일 큐브 구체가있는 장면부터 시작합니다. 처음부터 다시 만들거나 더 이상 필요없는 모든것을 제거하여 Cube Sphere의 장면을 계속 진행할 수 있습니다.

 

부드러운 변형을 얻으려면 구형에 정점이 있어야합니다. 구체의 격자 크기를 20으로 섲렁하고 반지름을 1로 설정하였습니다.

 

일반 CubeShpere로 시작하세요.

 

2. 메쉬 변형기

변형을 처리 할 새 스크립트 Mesh Deformer를 만듭니다. Cube Sphere 컴포넌트와 마찬가지로, 작업 할 메쉬 필터가 필요합니다.

using UnityEngine;

[RequireComponent(typeof(MeshFilter))]
public class MeshDeformer : MonoBehaviour {
}

새 구성 요소를 큐브 인스펙터에 추가하세요.

 

Mesh Deformer 컴포넌트가 추가된 Cube Sphere 입니다.

 

메쉬 필터만 있으면됩니다. 메쉬가 어떻게 생겼는지가 중요한 사항이 아니며, 따라서 어떤 메쉬라도 사용할 수 있습니다.

 

2.1 준비

변형을 수행하려면 메쉬에 엑세스해야합니다. 메쉬가 있으면 원래의 정점 위치를 추출 할 수 있습니다. 변형 도중 변위 된 정점을 추적해야합니다.

	Mesh deformingMesh;
	Vector3[] originalVertices, displacedVertices;

Start 메서드에서 메쉬와 정점을 가져 와서 원래의 정점을 옮겨진 정점에 복사합니다.

 

	void Start () {
		deformingMesh = GetComponent<MeshFilter>().mesh;
		originalVertices = deformingMesh.vertices;
		displacedVertices = new Vector3[originalVertices.Length];
		for (int i = 0; i < originalVertices.Length; i++) {
			displacedVertices[i] = originalVertices[i];
		}
	}

 

Awake에서 절차적 메쉬가 생성 될 수 있도록 Start를 사용하고 있는데, 이것은 항상 먼저 작동됩니다. 이 접근방식은 Awake의 객체를 관리하는 다른 구성 요소들에 의존하기 때문에 보장은 되지 않습니다. 또한 스클비트 실행 순서를 조정하여 누가 먼저 시작하고 마지막으로 시작하는지 설정할 수 있습니다.

 

2.2 정점 속도

정점은 메쉬가 변형되면서 움직입니다. 그래서 각 꼭지점의 속도를 저장해야합니다.

	Vector3[] vertexVelocities;

	void Start () {
		…
		vertexVelocities = new Vector3[originalVertices.Length];
	}

이제 우리는 변형을 지원하는 기본 요소를 갖게되었습니다.

파일 : 유니티 패키지

불러오는 중입니다...

3. Mesh Deformer Input

메쉬가 변형되는 방식을 제어할 방법이 필요합니다. 우리는 사용자 입력을 사용하므로 상호 작용합니다. 사용자가 객체를 터치 할 때마다 해당 시점에 강제 적용됩니다.

 

MeshDeformer 컴포넌트는 실제 변형을 담당하지만, 입력 방법에 대해 신경 쓰지 않습니다. 사용자 입력을 처리하기 위해 별도의 구성 요소를 만들어야 합니다. 구성 가능한 입력될 힘을 부여하세요.

using UnityEngine;

public class MeshDeformerInput : MonoBehaviour {

	public float force = 10f;
}

사용자의 시점을 나타내는 것 처럼 이 컴포넌트를 카메라에 연결하는 것이 가장 좋습니다. 변형 된 메쉬 객체에는 이 객체를 연결하지 않아야합니다. 왜냐하면 여러 개의 메쉬 객체가 장면에 있을 수 있기 때문입니다.

카메라에 부여된 Mesh Deformer Input 입니다.

 

3.1 입력 감지

기본 마우스 버튼을 누르고있을 때 마다 사용자 입력을 처리합니다. 따라서 클릭 또는 드래그가 있을 때마다 사용자가 누르고 있는 한 작동할 것 입니다.

 

	void  Update () {
		if ( Input .GetMouseButton ( 0 )) {
			HandleInput ();
		}
	}

이제 사용자가 가리키는 위치를 파악해야합니다. 우리는 카메라에서 장면으로 광선을 발사하여 이를 수행합니다. 장면의 주 카메라를 잡고 커서 위치를 광선으로 변환합니다.

	void HandleInput () {
		Ray inputRay = Camera.main.ScreenPointToRay(Input.mousePosition);
	}

우리는 물리 엔진을 사용하여 광선을 발사하고 그 광선이 부딪혔던 것에 대한 정보를 저장합니다. 광선이 무언가와 충돌 한 경우, 맞았던 MeshDeformer 대상 에서 구성요소를 검색할 수 있습니다.

		Ray inputRay = Camera.main.ScreenPointToRay(Input.mousePosition);
		RaycastHit hit;

		if (Physics.Raycast(inputRay, out hit)) {
			MeshDeformer deformer = hit.collider.GetComponent<MeshDeformer>();
		}
...더보기

Physics.Raycast가 어떻게 작동하나요?

Physics.Raycast는 3D 장면으로 광선을 발사하기 위한 정적 메서드입니다. 다양한 형태가 있지만 가장 간단한 버전은 ray 매개 변수를 가지며 무언가를 치는지 여부를 반환하는 것 입니다.

 

우리가 사용하는 버전에는 추가 매개 변수가 있습니다. 이것은 RaycastHit 형식의 출력 매개 변수입니다. 이 구조체에는 무엇이 적중되었는지와 접촉 지점에 대한 정보가 들어 있습니다.

3.2 힘 적용

우리가 무언가를 치고 그것이 MeshDeformer 컴포넌트를 가지고 있다면 우린 그것을 변형 시킬 수 있습니다. 그러니 계속해서 접촉점에 변형될 힘을 추가하십시오.

			MeshDeformer deformer = hit.collider.GetComponent<MeshDeformer>();
			if (deformer) {
				Vector3 point = hit.point;
				deformer.AddDeformingForce(point, force);
			}

물론 이 단계에선 아직 없지만, 여기선 MeshDeformer 컴포넌트에 AddDeformingForce 메소드가 있다고 가정합니다. 그러니 메서드를 추가하시면 됩니다. 다만 아직 번형은 하지 마세요. 우선 주 카메라에서 디버그 라인을 점으로 그려 레이를 시각화 합니다.

 

	public void AddDeformingForce (Vector3 point, float force) {
		Debug.DrawLine(Camera.main.transform.position, point);
	}

https://thumbs.gfycat.com/FearfulBiodegradableAltiplanochinchillamouse-mobile.mp4

불러오는 중입니다...

Scene view에서 ray 디버깅

 

...더보기

어디서 디버그 라인을 볼 수 있나요?

Scene view 에 표시되므로 재생 모드에서 Game view와 Scene view를 모두 보아야합니다.

 

3.3 Force Offset

우리가 생각해보고자하는 경험은 사용자가 메쉬를 찔러서 움푹 들어간 것 입니다. 이를 위해서는 접촉점 근처의 꼭지점이 표면에 밀여나야합니다. 그러나 변형력에는 고유 한 방향이 없습니다. 그것은 모든 방향으로 똑같이 적용될 것 입니다. 그러면 평평한 표면의 정점이 안쪽으로 밀리지 않고 수평으로 밀려납니다.

 

우린 힘의 점을 표면에서 당겨 방향을 추가 할 수 있습니다. 약간의 오프셋은 이미 꼭지점이 항상 서페이스로 푸시되도록 보장합니다. 접점의 법선을 오프셋 방향으로 사용할 수 있습니다.

오프셋을 사용하여 힘의 방향을 변경합니다.

	public float forceOffset = 0.1f;

	void HandleInput () {
		Ray inputRay = Camera.main.ScreenPointToRay(Input.mousePosition);
		RaycastHit hit;

		if (Physics.Raycast(inputRay, out hit)) {
			MeshDeformer deformer = hit.collider.GetComponent<MeshDeformer>();
			if (deformer) {
				Vector3 point = hit.point;
				point += hit.normal * forceOffset;
				deformer.AddDeformingForce(point, force);
			}
		}
	}

강제로 객체에서 약간 떨어진 지점

파일 : 유니티 패키지

불러오는 중입니다...

4. 기본 변형

실질적인 변위를 할 시간입니다. MeshDeformer.AddDeformingForce에서 현재 번위 된 모든 정점을 반복하고 각 정점에 번형력을 개별적으로 적용해야합니다.

	public void AddDeformingForce (Vector3 point, float force) {
		for (int i = 0; i < displacedVertices.Length; i++) {
			AddForceToVertex(i, point, force);
		}
	}

	void AddForceToVertex (int i, Vector3 point, float force) {
	}

 

4.1 힘에서 속도로의 변환

메쉬는 각 정점에 힘이 가해지기 때문에 변형됩니다. 정점이 밀리면 속도를 얻습니다. 시간이 지남에 따라 꼭지점의 위치가 모두 바뀝니다. 모든 정점이 똑같은 힘을 경험한다면, 전체 객체는 그 모양을 바꾸지 않고 움직일 것 입니다. 그러나 모든 정점은 힘을 달리 받습니다.

 

큰 폭발을 생각해보세요. 폭탄 바로 앞에 있다면 당신을 죽을 것이며 당신 근처에 있으면 당신은 날아갈 것이고, 충분히 멀리 있다면 아무런 영향이 없을 것 입니다. 즉, 힘은 거리에 따라 감쇠합니다. 방향의 차이와 함께 이 감쇠는 물체의 변형을 초랳바니다.

 

따라서 정점 당 변형력의 방향과 거리를 알아야합니다. 힘 포인트에서 정점 위치를 가리키는 벡터로부터 둘 다 파생될 수 있습니다.

 

	void AddForceToVertex (int i, Vector3 point, float force) {
		Vector3 pointToVertex = displacedVertices[i] - point;
	}

감쇠 된 힘은 역 제곱 법칙을 사용하여 알 수 있습니다. 원래의 힘을 거리의 제곱으로 나눕니다. 

사실, 여기선 1을 더한 거리 제곱으로 나눕니다.

이렇게 하면 거리가 0일 때 힘이 최대 강도로 유지됩니다. 그렇지 않으면 힘은 하나의거리에서 완전한 힘을 발휘할 것이고, 당신이 그 지점에 가까이 갈수록 무한대쪽으로 쏠 것 입니다.

		Vector3 pointToVertex = displacedVertices[i] - point;
		float attenuatedForce = force / (1f + pointToVertex.sqrMagnitude);

힘이 생겻으므로 속도 델타로 변환 할 수  있습니다. 실제로, 힘은 가속도로 변환됩니다.

그러면 속도의 변화는 다음과 같이 됩니다.

일을 간단하게 유지하기 위해 매 정점마다 하나 인 것처럼 질량을 무시합니다.  따라서 식은 다음과 같습니다.

		Vector3 pointToVertex = displacedVertices[i] - point;
		float attenuatedForce = force / (1f + pointToVertex.sqrMagnitude);
		float velocity = attenuatedForce * Time.deltaTime;

이 시점에서 우린 속도 델타를 갖지만 아직 방향은 아닙니다. 우리가 시작한 벡터를 정규화 함으로서 방향을 알아낼 수 있습니다. 그런 다음 정점 속도에 결과를 추가하면 됩니다.

		Vector3 pointToVertex = displacedVertices[i] - point;
		float attenuatedForce = force / (1f + pointToVertex.sqrMagnitude);
		float velocity = attenuatedForce * Time.deltaTime;
		vertexVelocities[i] += pointToVertex.normalized * velocity;

4.2 정점 이동

이제 정점에는 속도가 있으므로 이동시킬 수 있습니다. Update에서 각 정점을 처리하는 메소드를 추가합니다. 그런 다음 실제로 변위되도록 변위 정점을 메쉬에 지정합니다. 메쉬의 모양이 더 이상 상수가 아니기 때문에 법선을 다시 계산해야합니다.

	void Update () {
		for (int i = 0; i < displacedVertices.Length; i++) {
			UpdateVertex(i);
		}
		deformingMesh.vertices = displacedVertices;
		deformingMesh.RecalculateNormals();
	}

정점을 업데이트하는 것은 다음 수식을 통해 위치를 조정하는 것 입니다.

 

	void UpdateVertex (int i) {
		Vector3 velocity = vertexVelocities[i];
		displacedVertices[i] += velocity * Time.deltaTime;
	}
...더보기

정점은 매번 업데이트 해야 하나요?

네, 각 정점이 모두 업데이트되고 메쉬에 할당되며 법선이 다시 계산됩니다. 힘이 가해지지 않았을 때에도 말이죠. 사용자가 메쉬를 변형하지 않으면 시간 낭비라 생각할 수 있습니다. 점이 메쉬를 끊임없이 변형시킬 때만 사용하세요.

https://thumbs.gfycat.com/ResponsibleUnknownCooter-mobile.mp4

불러오는 중입니다...

누적되는 속도

파일 : 유니티 패키지

5. 형태 유지

정점은 이제 우리가 어떤 힘을 적용하자 마자 움직이기 시작합니다. 그러나 정점들은 멈추지 않고 계속 움직이며 객체의 원형은 사라지고 맙니다. 이제 객체를 원형으로 돌려놓아 보겠습니다.

 

실제 물체는 단단하고 변형되면서 압축되고 늘어납니다. 그들은 이 변형에 저항합니다. 아무런 방해를 받지 않고 원형으로 복원될 수 있습니다.

 

우리는 실제 볼륨을 갖고 있지 않습니다. 서피스를 설명하는 정점의 모음입니다. 이를 통해 현실적인 물리 시뮬레이션을 수행 할 수 없습니다. 그러나 그게 문제가 되진 않습니다. 우리가 정말로 필요하는 것은 신뢰가능 한 것 입니다.

 

5.1 스프링스

각 정점의 원래 위치와 변형 위치를 추적합니다. 우리가 각 버텍스의 두 버전 사이에 스프링을 부착한다고 가정 해보죠. 병형된 정점이 원점에서 멀리 떨어질 때마다 스프링이 원점을 뒤로 당깁니다. 변형된 정점이 멀어질 수록 스프링의 당김이 강해집니다.

 

배치 된 정점은 뒤로 당겨집니다.

변위 벡터를 구성 간으한 스프링 힘에 곱한 속도 조정으로 직접 사용할 수 있습니다. 이것은 간단하고 꽤 좋아 보입니다. 정점이 업데이트 될 때마다 이 작업을 수행합니다.

 

	public float springForce = 20f;
	
	void UpdateVertex (int i) {
		Vector3 velocity = vertexVelocities[i];
		Vector3 displacement = displacedVertices[i] - originalVertices[i];
		velocity -= displacement * springForce * Time.deltaTime;
		vertexVelocities[i] = velocity;
		displacedVertices[i] += velocity * Time.deltaTime;
	}

https://thumbs.gfycat.com/DependableSlightChick-mobile.mp4

불러오는 중입니다...

변형 후 다시 튀어오릅니다.

 

5.2 제동

정점은 이제 변형에 저항하고 원래의 위치로 돌아옵니다. 그러나 이 정점들은 반대로 튀어오르고, 끝없이 튀는 것을 반복합니다. 이것은 정점 자체를 보정하면서 속도를 높이는 동안 스프링이 계속 당겨지기 때문에 발생합니다. 너무 멀리 뒤로 움직이면 속도가 느려져야합니다.

 

우리는 끊임없이 정점을 낮추어 영원한 진동을 막을 수 있습니다. 이 댐핑 효과는 저항, 항력, 관성 등을 대체합니다. 이것은 시간에 따라 속도를 감소시키는 단순한 요인이며 댐퍼닝이 높을수록 덜 탄력 있고 느린 물체가 됩니다.

	public float damping = 5f;
	
	void UpdateVertex (int i) {
		Vector3 velocity = vertexVelocities[i];
		Vector3 displacement = displacedVertices[i] - originalVertices[i];
		velocity -= displacement * springForce * Time.deltaTime;
		velocity *= 1f - damping * Time.deltaTime;
		vertexVelocities[i] = velocity;
		displacedVertices[i] += velocity * Time.deltaTime;
	}

https://thumbs.gfycat.com/LegalMeatyDolphin-mobile.mp4

불러오는 중입니다...

원형 복원이 됩니다.

 

파일 : 유니티 패키지

불러오는 중입니다...

 

6. 변환 다루기

메쉬 변형은 이제 개체를 변형 할 때를 제외하고 완전히 기능합니다. 모든 계산은 로컬 공간에서 수행됩니다. 구를 움직이거나 회전 시키십시오. 변형되는 힘이 잘못 적용될 것 입니다.

 

우리는 물체의 변형을 보상해야합니다. 우린 변형체의 위치를 월드 공간에서 로컬 공간으로 변환하여 이 작업을 수행합니다.

	public void AddDeformingForce (Vector3 point, float force) {
		point = transform.InverseTransformPoint(point);
		for (int i = 0; i < displacedVertices.Length; i++) {
			AddForceToVertex(i, point, force);
		}
	}

https://thumbs.gfycat.com/EnchantedGracefulCaribou-mobile.mp4

불러오는 중입니다...

좌표는 다르지만 다른 척도를 수정합니다.

 

6.1 규모 조정

이제 힘이 정확한 위치에 적용되지만 다른 것은 여전히 잘못되었습니다. 구체를 위아래로 균일하게 스케일합니다. 변형은 같은 양만큼 비례 함을 알 수 있습니다. 이는 올바르지 못합니다. 크고 작은 물체는 같은 물리를 받아야 합니다.

 

우린 물체의 규모를 보완해야합니다. 첫째, 일정한 규모를 알아야합니다. 변환의 로컬 축 중 하나를 확인하여 찾을 수 있습니다. 이 작업을 각각 업데이트하여 규모를 동적으로 변경하는 객체로 다소 작업 할 수 있습니다.

 

...더보기

비 균일 Scale은 어떤가요?

눈금에 단일 값 대신 3D 벡터를 사용할 수 있습니다. 그런 다음 각 측정 기준을 개별적으로 조정하십시오. 그러나 실제로, 당신은 비 균일 한 Scale을 다루고 싶지 않습니다.

이제 벡터를 균일 한 스케일로 수정하세요. 아래와 같이 하면 올바른 거리를 사용할 수 있습니다.

	void AddForceToVertex (int i, Vector3 point, float force) {
		Vector3 pointToVertex = displacedVertices[i] - point;
		pointToVertex *= uniformScale;
		float attenuatedForce = force / (1f + pointToVertex.sqrMagnitude);
		float velocity = attenuatedForce * Time.deltaTime;
		vertexVelocities[i] += pointToVertex.normalized * velocity;
	}

동일한 작업을 UpdateVertex 에서 displacement에 수행합니다. 이제 속도는 더욱 정확해졌습니다.

	void UpdateVertex (int i) {
		Vector3 velocity = vertexVelocities[i];
		Vector3 displacement = displacedVertices[i] - originalVertices[i];
		displacement *= uniformScale;
		velocity -= displacement * springForce * Time.deltaTime;
		velocity *= 1f - damping * Time.deltaTime;
		vertexVelocities[i] = velocity;
		displacedVertices[i] += velocity * Time.deltaTime;
	}

그러나 스케일이 조정되지 않은 객체에 대해서는 속도가 정확합니다. 우리의 객체가 실제로 크기가 조정됨에 따라 정점 이동을 조정해야합니다. 이번에는 곱하기 대신 나누어야합니다.

		displacedVertices[i] += velocity * (Time.deltaTime / uniformScale);

https://thumbs.gfycat.com/TartDelectableAracari-mobile.mp4

불러오는 중입니다...

다른 가늠자, 동일한 물리학

 

모든 위치, 회전 및 균일한 크기에서 작동하는 변형 메쉬입니다. 이것은 간단하고 상대적으로 저렴한 시각적 효과임을 명심하십시오. 연성 물리 시뮬레이션이 아닙니다. 객체의 충돌자는 변경되지 않으므로 물리 엔진은 객체의 변형된 모양을 인식하지 못합니다.

 

파일 : 유니티 패키지

불러오는 중입니다...

파일 : PDF

반응형