물리연산은 굉장히 많이쓰이고 연산량이 많은 부분이다.
그래서 더욱 더 최적화적으로 중요한 부분인데 관련해서 한번 정리를 해보자
1. 물리연산 켜기/끄기
==========================================================================
● Unity는 물리 연산 관련 컴포넌트가 없더라도 물리엔진에 의한 물리 연산 처리는 매 프레임마다 반드시 실행된다.
따라서 게임 내에서 물리 연산이 필요하지 않은 경우 물리 엔진을 꺼두는 것이 좋다.
물리 엔진의 처리는 Physics.autoSimulation 에 값을 설정하여 켜고 끌 수 있다. 예를 들어, 게임 내에서만 물리 연산을 사용하고 그 외에는 물리 연산을 사용하지 않는다면, 게임 내에서만 이 값을 true를 사용하면 된다.
2. Fixed Timestep, Fixed Update의 빈도 최적화
==========================================================================
● MonoBehaviour의 FixedUpdate는 Update와 달리 고정된 시간으로 실행된다.
물리 엔진은 이전 프레임의 경과 시간에 대해 1 프레임 내에 FixedUpdate를 여러번 호출하여 부하가 발생 하게 된다.
◎ Timestep 의 값에 따라 Fixed Update가 호출되는 빈도가 달라지는데 해당 값의 단위는 초다. 기본값은 0.02 기존에 알고 있던 값인데 일반적으로 작을수록 물리 연산의 정확도가 높아져 콜리전 누락과 같은 문제가 발생하기 힘들지만 성능상 이슈가
발생하는 경우가 있어 게임 동작에 문제가 생기지 않는 범위 내에서 목표 FPS 에 가까운 값을 설정하는 것이 바람직하다.
3. Maximum Allowe Timestep
==========================================================================
● Fixed Update는 이전 프레임으로부터의 경과 시간을 기준으로 여러번 호출이 된다.
"특정 프레임에서 렌더링 처리가 많이 진행되었다"는 등의 이유로 이전 프레임의 경과 시간이 길어지면 해당 프레임에서
Fixed Update가 여러번 실행이 될 수 있다.
예시를 들자면 Fixed Timestep이 20mm sec이고 이전 프레임에 200 밀리초가 걸렸다면 Fixed Update 는 10번 호출이 될 것 이다. 즉 한프레임에서 처리 실패가 발생하면 다음 프레임의 물리 연산 비용이 높아지는데 이로 인해 그 프레임도 처리 중단될 위
험이 높아져 다음 프레임의 물리 연산이 더 무거워지는 등 부정적인 무한루프에 빠지게 되어버린다.
이 문제를 해결하기 위해 유니티에서는 Projectsettings에서 Maximum Allowd Timestep 라는 1프레임 내에서 물리 연산이 사용 할 수 있는 최대 시간을 설정할 수 있다.
즉 누적된 물리엔진의 연산들이 내가 지정한 초를 넘어서면 나머지부분들은 잘라서 연산량이 늘어나는 현상이나 프레임 처리 실패가 되는 현상을 방지하는 것이다.
4. Collider form
==========================================================================
● 충돌 판정 처리 비용은 콜리전 모양과 그 상황에 따라 달라진다. 그 비용을 일률적으로 말할 수는 없지만,
대략적으로 판단 비용이 낮은 순서대로 구 => 캡슐 => 박스 => 메쉬 순서대로이다.
예를 들어, 사람 모양의 캐릭터 형상을 근사화하기 위해 캡슐 콜리더를 많이 사용하지만, 게임상 키가 사양에 영향을 주지않는다면, 스피어 콜리더로 대체하는게 판정 비용이 더 적게 든다.
여기서 메쉬 콜리더는 부하가 높다는 점에 꼭 유의를 해야하며 우선적으로 구형을 시도해보고 또는 캡슐 후에 박스 콜리더를
조합하는 방식으로 해야한다. 그래도 맞지 않다면 그때 메쉬 콜리더를 사용하면 된다.
● 그런데 왜 박스의 연산량이 더 높은걸까?
◎ 박스 콜리더가 회전할시 OBB연산이 추가적으로 진행이 되기 때문이다. 따로 회전을 진행하지 않을떄는 AABB를 사용해서 비교적으로 가벼운 상태이지만 회전할때 OBB연산을 진행해서 무거워진다.
● AABB연산이란?(Axis-Aligned Bounding Box)
기저 축에 정렬된 충돌 박스이다.
( 3D 공간은 수학적으로 3개의 선형 독립인 축으로 구성된 좌표계로 표현되는데
이 세 축을 각각 기저 벡터(Basis Vector) 또는 **기저 축(Basis Axis)**이라고 부른다. )
박스를 이루는 면의 노말 벡터들이 곧 X Y Z 축과 일치하는 모양이다.
모델을 이루는 다각형의 x,y,z 좌표의 최소 최대를 각 박스의 버텍스로 해서 생성한다.
따라서 회전함에 따라 크기가 계속 변하게 된다.
축에 일치된 모양이기 때문에 AABB끼리의 충돌 검출은 매우 간편하다.

● OBB(Oriented Bounding Box)
AABB와 마찬가지로 OBB는 박스를 이루는 세면은 수직이지만 해당 면의 노말 벡터가 XYZ와 일치하지 않는 박스이다.
XYZ와 일치하지 않기 떄문에 AABB보다 충돌 검출의 시간 복잡도가 높지만, 충돌 박스가 XYZ 좌표를 기준으로 최대,최소로
계속 변하는 AABB보다 항상 좀 더 fit한 바운딩 박스를 만들 수 있다.

즉 AABB는 노말벡터가 변하지 않기 때문에 세계좌표 기준으로 진행이되며 min,max로만 충돌을 구분하기 때문에 연산량이 적고
OBB는 회전하면서 노말벡터가 수시로 변하기 때문에 지역좌표(기저축) 기준으로 진행이되고 SAT등 복잡한 방식으로 연산이 되어 연산량이 많다는 점이 특징이다.대신 모델에(타이트) 맞게 충돌처리가 가능하다.
5. 충돌 레이어 최적화
===========================================================================
Physics 에는 어떤 게임 오브젝트의 레이어끼리 충동할 수 있는지를 정의하는 '충돌 메트릭스'라는 설정이 있다.
이 설정은 Physics > Layer Collision Matrix 에서 변경이 가능하다.

충돌 매트릭스는 두 개의 레이어가 교차하는 위치의 체크박스에 체크가 되어 있으면 해당 레이어가 충돌한다는 것을 나타낸다.
충돌하지 않는 레이어 간에는 브로드 페이즈라고 하는 오브젝트의 대략적인 맞물림 판단을 하는 전 계산에서도 제외하여 이 설정을 적절히 설정하여 충돌할 필요가 없는 오브젝트 간 계산을 생략하는게 효율적이다.
성능을 고려하면 물리 연산은 전용 레이어를 마련하고, 충돌할 필요가 ㅇ벗는 레이어간의 체크 박스는 모두 해제한다.
6. 레이 캐스트
===========================================================================
● 레이캐스트는 선분으로 충돌 판정을 하는 Physics.Raycast 외에도 Physics.SphereCast 등 다른 모양으로 판정을 하는
메소드가 준비되어 있다. 다만 판정을 내리는 형상이 복잡할수록 부하가 상당해져 Physics.Raycast를 사용하는것이 좋다.
● Physcis.Raycast 에서는 충돌한 콜리더 중 하나의 충돌 정보를 반환하지만, Physics.RaycastAll 메서드를 이용하면 여러개의 충돌 정보를 얻을 수 있다.
여기서 Physics.RaycastAll은 충돌 정보를 RaycastHit의 구조체 배열을 동적으로 확보하여 반환하는데. 이 기능을 호출할 때
마다 GC Alloc이 발생하여 GC에 의한 스파이크의 원인이 된다.
해당 문제를 해결하기 위해 이미 확보된 배열을 인수로 전달하면 결과를 해당 배열에 쓰고 반환하는 Physics.RaycastNonAlloc 메서드가 존재하는데 성능을 고려하면 FixedUpdate 내에서는 가급적 GC Alloc 을 발생시키지 않아야 한다.
예시 로직
// 레이를 날릴 시작점
var origin = transform.position;
// 레이를 날릴 방향
var direction = Vector3.forward;
// 레이의 길이
var maxDistance = 3.0f;
// 레이가 충돌할 대상 레이어
var layerMask = 1 << LayerMask.NameToLayer("Player");
// 레이캐스트의 충돌 결과를 저장하는 배열
// 이 배열을 초기화할 때 미리 확보하거나,
// 풀에 확보되어 있는 것을 이용한다.
// 레이캐스트 결과의 최대 개수를 미리 결정해야 한다.
private const int kMaxResultCount = 100;
private readonly RaycastHit[] _results = new RaycastHit[kMaxResultCount];
// 모든 충돌 정보가 배열로 반환된다.
// 반환값은 충돌 개수
var hitCount = Physics.RaycastNonAlloc(
origin,
direction,
_results,
maxDistance,
layerMask
);
if (hitCount > 0)
{
Debug.Log($"{hitCount}명의 플레이어와 충돌했습니다");
// _results 배열에는 충돌 정보가 순서대로 저장된다.
var firstHit = _results[0];
// 이후 firstHit를 통해 충돌 위치나 대상 등을 확인할 수 있다.
}
6. 콜리더와 Rigidbody
==========================================================================
● 콜리더의 종류
◎ 콜리더 컴포넌트와 강체 기반의 물리 시뮬레이션을 위한 리지드바디 컴포넌트가 있다. 이 컴포넌트들의 조합과 설정에
따라 세가지 콜리더로 분류가된다.
◎ 콜리더는 붙어있지만 리지드바디가 없는 오브젝트는 정적 콜리더 (static Colider)라고 한다. 이 콜리더는 항상 같은 위치에 머무는, 움직이지 않는 지오메트리에만 사용한다는 전제로 최적화가 이루어진다. 게임 중에 스태틱 콜리더를 활성화/ 비활성화 하거나, 이동 및 스케일링 해서는 안된다. 이러한 작업을 수행하면 내부 데이터 구조 변경에 따른 재계산이 발생하여 성능이 크게저하될 수 있다.
◎ 콜리더와 리지드바디 컴포넌트가 모두 부착되어 있는 객체는 동적 콜리더라고 한다. 이 콜리더는 물리 엔진에 의해 다른 오브 젝트와 충돌할 수 있으며 스크립트에서 Rigidbody 컴포넌트를 조작하여 가해지는 충돌이나 힘에 반응할 수 있다.
◎ 콜리더와 리지드바디 컴포넌트가 모두 부착되어 있으며 isKinematic속성을 활성화한 객체를 키네마틱 다이나믹 콜리더로 분
류한다. 키네마틱 다이나믹 콜리더는 Transform 컴포넌트를 직접 조작하여 움직일 수 있지만, 일반 다이나믹 콜리더처럼
Rigidbody 컴포넌트를 조작하여 충돌이나 힘을 가하여 움직일 수 없다. 물리연산 최적화 부분에서 자주 사용된다.
7. Rigidbody와 슬립 상태
==========================================================================
● 물리 엔진에서는 최적화의 일환으로 RIgidbody 컴포넌트를 부착한 오브젝트가 일정 시간 동안 움직이지 않을 경우,
해당 오브젝트는 휴먼 상태라고 판단하여 해당 오브젝트의 내부 상태를 슬립 상태로 변경한다. 슬립 상태로 전환되면 외력이나 충돌등의 이벤트에 의해 움직이지 않는 한 해당 오브젝트에 대한 계산 비용을 최소화할 수 있다.
● 슬립상태로 전환되는 임계값은 Rigidbody.sleepThreshold 속성에서 설정할수있다.
sleepThereshold 속성의 값을 높이면 오브젝트가 더 빨리 슬립 상태로 전환되어 계산 비용을 낮출 수 있다.
하지만 천천히 움직이고 있는 경우에도 슬립 상태로 전환이 될 경향이 있기 떄문에 오브젝트가 정지가 된 것 처럼 보일 수 있다.
이 값을 낮추면위와 같은 현상이 발생하기 어려워지지만,한편으로는 전환하기 어려워져 계산 비용을 낮추기 어려워지는 경향이
있다.

Profiler의Physics항목. 활성화된Rigidbody의개수뿐만아니라물리엔진에있는 각 요소의 개수를 확인할 수 있다.

Physics Debugger, 씬의 오브젝트가 물리엔진 상에서 어떤 상태인지 색상으로 구분되어 표시된다.
8. 충돌 감지 최적화
==========================================================================
현재(2025.06.15) 유니티는
총 4개의 충돌처리 알고리즘이 있다.
• Discrete(이산처리)
• Continuous(연속처리)
• Continuous Dynamic
• Continuous Speculative
● Discrete는 이산적 충돌 판저으 나머지는 연속적 충돌 판정에 속한다.
이산적 충돌 판정은 이름 그대로 시뮬레이션 마다 오브젝트가 이산적으로 텔레포트 이동을 하고, 모든 오브젝트가 이동한 후
충돌 판정을 하여 속도가 빠른 오브젝트 같은 경우에는 충돌을 놓쳐서 빠져나갈 가능성이 있다.
반면 연속 충돌 판정은 이동 전후의 오브젝트 충돌을 고려하기 때문에 고속으로 움직이는 오브젝트의 미끄러짐을 방지한다.
그만큼 이산적 충돌 판단에 비해 계산 비용이 높아진다. 성능을 최적화 하려면 가능한 한 Discrete를 선택할 수 있도록 게임
동작을 만들어야 한다.
● Continuous는 은동적(연속적으로 움직이는 콜리더) 와 정적 콜리더의 조합에 대해서만 연속적인 충돌 판단이 활성화 된다.
Continuous Dynamic은 동적 콜리더끼리도 연속적 충돌 판정이 활성화된다. 계산비용은 Contibuous Dynamic이 더 높다.
따라서 캐릭터가 필드를 돌아다니는, 즉 동적 콜리더와 정적 콜리더의 충돌 판정만 고려한다면 Continuous를 선택하고, 움직이는 콜리더 간의 스플라이스도 고려하고 싶다면 COntinuous Dynamic을 선택하면 된다.
Continuous Speculative은 동적 콜리더 간 연속적인 충돌 판단이 유효함에도 불구하고 Continuous Dynamic 보다 계산 비용이 낮지만, 여러 콜리더가 근접한 곳에서 오충돌하는 고스트 콜리더라는 현상이 발생하므로 도입시 주의가 필요하다.
즉 Contious Dynamic 같은 경우는 연속적으로 충돌을 검사를 하지만 Continous Speculative는 다음 위치를 연속적으로 계산해서
충돌을 하여 연산량이 낮은편인데 정확한 충돌처리에는 맞지 않는 부분이 특징.
9. 설정 최적화
==========================================================================
● Physics.autoSuncTransforms
◎ Unity 2018.3 이전 버전에서는 Physics.Raycast등의 물리 연산 관련 API를 호출할 때마다 Transform과 물리 엔진의
위치가 자동으로 동기화 되었다.
이 처리는 비교적 무거운 작업이기 때문에 물리 연산 API를 호출할 때 스파이크가 발생할 수 있었다.
이 문제를 해결하기 위해 Unity 2018.3 부터 Physics.autoSuncTransforms라는 설정이 추가 되었다.
이 값에 false를 설정하면 위에서 설명한 물리 연산 API 호출 시 Transform의 동기화 처리가 이루어지지 않는다.
◎ 과거에 물리엔진을 사용했을때 동기화를 사용한 이유는 엔진 사용성의 편의와 정확한 탐지를 위해 사용했지만
게임이 무거워지고 다른 기능들이 발전함에 따라 스파이크의 주요 원인이 되어서 해당 기능을 넣게 되었다.
● Physics.reuseCollisionCallbacks
◎ Unity 2018.3 이전 버전에서는 OnCollisionEnter 등 Collider 컴포넌트의 충돌 판정을 받는 이벤트가 호출될 때마다 인수의 Collision 인스턴스를 새로 생성하여 전달하기 떄문에 GC Alloc이 발생했다.
이 동작은 이벤트 호출 빈도에 따라 게임 성능에 악 영향을 줘서 2018.3 이후 Physics.reuseCollisionCallbacks라는
속성이 새롭게 공개되었다. 이 값에 true를 설정하면 이벤트 호출 시 전달되는 Collision인스턴스를 내부에서 순회하여 사용하므로 GC Alloc을 억제할 수 있다.
'최적화' 카테고리의 다른 글
| 오브젝트 풀 (Object Pool) (8) | 2025.08.13 |
|---|---|
| 튜닝<그래픽> (1) | 2025.06.17 |
| 에셋 관련 최적화 (1) | 2025.06.01 |
| 튜닝 기본 상식(2) (0) | 2025.05.17 |
| 최적화<튜닝>의 기본상식1 (4) | 2025.05.11 |