본문 바로가기
PhysX/[NVIDIA] PhysX_Tutorial

6. PhysX - RigidBody 충돌 - 2

by 헛둘이 2023. 3. 3.

Broad-phase 알고리즘

 
PhysX는 다음과 같은 여러 개의 Broad-phase 알고리즘을 지원합니다.
 
- sweep-and-prune (SAP)
- multi box pruning (MBP)
- automatic box pruning (ABP)
 
SAP
- SAP는 객체의 많은 부분이 움직이지 않을 때 훌륭한 성능을 발휘한다.
- 하지만 모든 객체가 움직이거나 많은 수의 객체가 Broad-phase에 추가되거나 제거될 때 성능이 저하된다.
 
MBP
- 모든 객체가 움직일 때 또는 많은 수의 객체를 삽입할 때 SAP와 같은 성능 문제가 발생하지 않는다.
- 하지만 많은 수의 객체가 움직이지 않을 때 성능은 SAP보다 떨어질 수 있다.
(이 알고리즘을 수행하기 위해 충돌을 계산하는데 사용하는 공간을 정의해야 한다)
(이런 경계를 정의함으로써 충돌 검출이 더 효율적으로 동작한다.
 
ABP
- ABP는 SAP의 편의성과 MBP의 성능을 결합하여 제공한다.
- 많은 수의 객체가 움직이지 않을 때 SAP가 더 빠르고, MBP가 올바르게 정의된 영역을 사용할 때 더 빠를 수 있지만,
- ABP는 평균적으로 최상의 성능과 최상의 메모리 사용량을 제공한다.
 
이 알고리즘은 PxSceneDesc 구조체 내의 PxBroadPhaseType 열거형으로 제어된다.
 
 


관심 지역 (Regions of Interest)

 
- 객체가 속한 AABB 영역을 기준으로 Broad-phase 알고리즘이 객체를 처리한다.
- 이 때, Regions of Interest를 사용하면 Broad-phase가 관심 영역 내의 객체만 처리하도록 제한 가능하다.
- 이렇게 영역을 제한하면, 영역 밖에 있는 객체는 충돌 검출에서 제외됨
- 관심 지역을 사용하면, 시뮬레이션 공간 전체를 커버하면서도, 비어있는 공간의 양을 최소화할 수 있다.
- 관심 지역들은 겹칠 수 있으나 겹치지 않는 게 성능 향상에 더 도움이 된다.
 
관심 지역은 PxBroadPhaseRegion 구조체를 사용해서 정의된다.
사용자 지정 데이터를 할당할 수 있고, 시뮬레이션 생성 시간에 PxScene::addBroadRegion 함수를 통해 런타임에 정의 가능하다.
SDK는 새로 생성된 영역에 대한 핸들을 반환하며, 이 핸들을 통해 나중에 영역을 제거할 수 있다.
(PxScene::removeBroadPhaseRegion)
 
*관심 지역은 PxBroadPhaseType::eMBP 알고리즘을 사용해야만 한다. (SAP, ABP에서는 정의X)
 
범위를 벗어난 객체를 처리하는 콜백 (Broad-phase Callback)
- 이 콜백은 PxBroadPhaseCallback 객체로, PxSceneDesc 구조체 내에 정의된다.
- Broad-phase callback은 주로 넓은 범위의 충돌 감지를 위해 사용된다.
- Broad-phase 알고리즘이 객체들 간의 충돌을 검사하기 전에, AABB 등의 충돌 체크용 형태로 나눠진 구역을 정의하게 된다.
- 이 구역 안에 있는 객체만 충돌 감지가 가능하며, 범위가 벗어난 객체는 검사에서 제외된다.
- 이 제외된 객체를 처리하는 콜백이 Broad-phase callback이다.
- 이 객체를 처리하는 방법은 사용자에게 달려 있으며 아래와 같다.
 
1. 객체를 삭제한다.
2. 객체가 다시 올바른 범위로 진입할 때까지 충돌 검사를 비활성화하고 객체가 이동할 수 있도록 한다.
3. 객체를 인위적으로 다시 올바른 위치로 이동시킨다.
 
 


충돌 필터링

 
PhysX 4.1에서는 충돌 필터링 기능을 제공한다.
일부 객체 쌍을 충돌 테스트에서 제외하거나, 충돌 테스트를 시행하는 객체 쌍에 대해 특정한 충돌 감지 동작을 구성할 수 있도록 한다.
이를 통해 특정 상황에서 특정 객체 간의 충돌 여부를 결정할 수 있다!
 
PhysX 3.0이상부터 벡터 프로세서에서 실행되는 코드를 사용해서 규칙을 구현할 수 있는 쉐이더 시스템과,
유연한 콜백 메커니즘을 모두 제공한다.
 
SampleSubmarine에서 구현한 필터 쉐이더 코드

PxFilterFlags SampleSubmarineFilterShader(
    PxFilterObjectAttributes attributes0, PxFilterData filterData0,
    PxFilterObjectAttributes attributes1, PxFilterData filterData1,
    PxPairFlags& pairFlags, const void* constantBlock, PxU32 constantBlockSize)
{
    // let triggers through
    if(PxFilterObjectIsTrigger(attributes0) || PxFilterObjectIsTrigger(attributes1))
    {
        pairFlags = PxPairFlag::eTRIGGER_DEFAULT;
        return PxFilterFlag::eDEFAULT;
    }
    // generate contacts for all that were not filtered above
    pairFlags = PxPairFlag::eCONTACT_DEFAULT;

    // trigger the contact callback for pairs (A,B) where
    // the filtermask of A contains the ID of B and vice versa.
    if((filterData0.word0 & filterData1.word1) && (filterData1.word0 & filterData0.word1))
        pairFlags |= PxPairFlag::eNOTIFY_TOUCH_FOUND;

    return PxFilterFlag::eDEFAULT;
}

 
- PxFilterObjectAttributes는 물체의 타입 정보를 인코딩하는 32비트 데이터이다.
- 예를 들어, PxFilterObjectType::eRIGID_STATIC 또는 eRIGID_DYNAMIC을 담을 수 있다.
- 이 정보를 통해 물체가 Kinematic인지, Trigger인지 판단할 수 있다.
- PxFilterData는 필터링에 사용되는 데이터를 담기 위해 설계되었다.
- 이 데이터를 사용해서 충돌 그룹을 설정하거나 충돌 필터링을 구현할 수 있다.
- 함수 내에서 포인터를 사용하지 않는 이유는 쉐이더 함수가 원격 프로세서에서 컴파일되어 실행될 수 있기 때문이다.
 

// let triggers through
if(PxFilterObjectIsTrigger(attributes0) || PxFilterObjectIsTrigger(attributes1))
{
    pairFlags = PxPairFlag::eTRIGGER_DEFAULT;
    return PxFilterFlag::eDEFAULT;
}

- 두 객체 중 하나라도 Trigger되면, 트리거 동작을 실행하고, 그렇지 않으면 기본 충돌 테스트를 실행한다.
 
 

// generate contacts for all that were not filtered above
pairFlags = PxPairFlag::eCONTACT_DEFAULT;

// trigger the contact callback for pairs (A,B) where
// the filtermask of A contains the ID of B and vice versa.
if((filterData0.word0 & filterData1.word1) && (filterData1.word0 & filterData0.word1))
    pairFlags |= PxPairFlag::eNOTIFY_TOUCH_FOUND;

return PxFilterFlag::eDEFAULT;

- 위 코드는 다른 모든 객체에 대해 기본 충돌 처리를 수행한다는 의미이다.
- 또한 filterData에 따라 특정 쌍에서 접축 알림을 요청하는 규칙이 있다.
- 이게 무슨 의미인지 이해하려면 이 샘플에서 filterData에 대해 알아야 한다.
 

struct  FilterGroup 
{ 
    enum  Enum 
    { 
        eSUBMARINE      =  ( 1  <<  0 ), 
        eMINE_HEAD      =  ( 1  <<  1 ), 
        eMINE_LINK      =  ( 1  <<  2 ), 
        eCRAB           =  ( 1  <<  3 ), 
        eHEIGHTFIELD    =  ( 1  <<  4 ) , 
    }; 
};

- 샘플 코드는 열거형을 사용해서 플래그를 표현하고 있다.
- 그리고 아래 코드에서 PxFilterData::word0을 이 FilterGroup에 할당해서 각 Shape의 유형을 식별한다.
 

void setupFiltering(PxRigidActor* actor, PxU32 filterGroup, PxU32 filterMask)
{
    PxFilterData filterData;
    filterData.word0 = filterGroup; // word0 = own ID
    filterData.word1 = filterMask;  // word1 = ID mask to filter pairs that trigger a
                                    // contact callback;
    const PxU32 numShapes = actor->getNbShapes();
    PxShape** shapes = (PxShape**)SAMPLE_ALLOC(sizeof(PxShape*)*numShapes);
    actor->getShapes(shapes, numShapes);
    for(PxU32 i = 0; i < numShapes; i++)
    {
        PxShape* shape = shapes[i];
        shape->setSimulationFilterData(filterData);
    }
    SAMPLE_FREE(shapes);
}

- 이렇게 하면 각 Shape의 PxFilterDatas가 설정된다.
- 이걸 사용하는 몇 가지 예는 아래 코드를 참고하면 된다.
 

setupFiltering(mSubmarineActor, FilterGroup::eSUBMARINE, FilterGroup::eMINE_HEAD |
    FilterGroup::eMINE_LINK);
setupFiltering(link, FilterGroup::eMINE_LINK, FilterGroup::eSUBMARINE);
setupFiltering(mineHead, FilterGroup::eMINE_HEAD, FilterGroup::eSUBMARINE);

setupFiltering(heightField, FilterGroup::eHEIGHTFIELD, FilterGroup::eCRAB);
setupFiltering(mCrabBody, FilterGroup::eCRAB, FilterGroup::eHEIGHTFIELD);

- 이 방식이 유연하지 못하다고 느껴지면 PxSimulationFilterCall과 함께 제공되는 콜백 접근 방식을 사용하는 게 좋다.
 
 
- 아래는 필터 쉐이더를 사용하는 예시다.

// Filtering shader function
PxFilterFlags MyFilterShader(
    PxFilterObjectAttributes attributes0, PxFilterData filterData0,
    PxFilterObjectAttributes attributes1, PxFilterData filterData1,
    PxPairFlags& pairFlags, const void* constantBlock, PxU32 constantBlockSize)
{
    // Allow triggers to pass through without any additional handling
    if (PxFilterObjectIsTrigger(attributes0) || PxFilterObjectIsTrigger(attributes1))
    {
        pairFlags = PxPairFlag::eTRIGGER_DEFAULT;
        return PxFilterFlag::eDEFAULT;
    }

    // Generate contacts for all other pairs
    pairFlags = PxPairFlag::eCONTACT_DEFAULT;

    // If the two objects have the same filter group, disable contact generation
    if (filterData0.word0 == filterData1.word0)
    {
        pairFlags = PxPairFlag::eCONTACT_DEFAULT | PxPairFlag::eNOTIFY_TOUCH_LOST;
        return PxFilterFlag::eDEFAULT;
    }

    // Otherwise, notify touch found if the filter masks allow collision between the objects
    if ((filterData0.word1 & filterData1.word0) && (filterData1.word1 & filterData0.word0))
    {
        pairFlags |= PxPairFlag::eNOTIFY_TOUCH_FOUND;
    }

    // Return the default filter flag
    return PxFilterFlag::eDEFAULT;
}

// Create a simulation filter callback
PxSimulationFilterCallback* filterCallback = new PxSimulationFilterCallback();
filterCallback->registerPairFilterCallback(PxFilterObjectFlag::eRIGID_BODY, PxFilterObjectFlag::eRIGID_BODY, MyFilterShader);

 
-  이 예제에서는 두 개의 객체가 충돌할 때 호출되는 필터링 쉐이더 함수를 정의하고, 이 함수를 사용하여 PxSimulationFilterCallback 객체를 만들며, 이 객체를 사용해서 물리 시뮬레이션에 필터링 규칙을 등록한다.
- 그리고 PxFilterObjectFlag를 사용해서 필터링할 객체 유형을 정의하고, 이러한 유형에 대한 필터링 규칙을 쉐이더 함수와 함께 등록한다.
- 이 예제에서는 간단한 필터링 로직만 보여주기 위해 객체 유형이 PxFilterObjectFlag::eRIGID_BODY인 경우에만 필터링 쉐이더 함수를 등록하고 있다.
 
 


Aggregates

 

- Aggregate란 여러 개의 액터들의 모임이다.
- 이 액터들을 Aggregate로 묶으면 SDK에게 이 액터들이 서로 연관되어 있다는 정보를 제공할 수 있다.
- Aggregate를 사용하는 대표적인 예시는 랙돌이다.
- 랙돌은 여러 개의 다른 액터들로 이루어져 있는데, Aggregate를 사용하지 않으면, 모든 형상마다 Broad-phase entry가 생기게 된다. (랙돌의 액터 각각이 충돌 체크를 하는 현상)
- 이런 경우 브로드 페이즈에서 하나의 엔티티로 처리해서 성능을 최적화할 수 있다. 
- 만약 액터들간에 충돌이 필요하지 않다면, 생성 시에 충돌을 비활성화할 수 있다.
- 주로 정적이거나 키네마틱한 액터들의 Aggregate를 만드는 게 일반적이다. (벽, 바위, 건물 등)
- 정리하면.. Actor를 단위로 묶는 개념인듯?
*액터의 최대 개수와 자체 충돌(self-collision) 속성은 변경할 수 없다.
 
*자체 충돌(self-collision)이란?
- 액터가 자신의 다른 형상과 충돌할 수 있는지 여부를 결정하는 속성
(ex. 랙돌의 각 관절은 서로 충돌하지 않아야 함)
 
 
 

Aggregate 생성

 
- 다음은 Aggregate를 생성하는 코드다.

PxPhysics* physics; // The physics SDK object

PxU32 nbActors;     // Max number of actors expected in the aggregate
bool selfCollisions = true;

PxAggregate* aggregate = physics->createAggregate(nbActors, selfCollisions);

- 현재 액터 최대 수는 128로 제한되어 있으며, 효율성을 위해 가능한 낮게 유지해야 한다.
- 만약 액터간 충돌이 필요하지 않다면, 생성 시점에 충돌 기능을 비활성화 시켜야 한다.
- 내부 필터링 로직을 모두 건너뛰기 때문에 훨씬 효율적임 
 
 

Aggregate 채우기

PxActor& actor;    // Some actor, previously created
aggregate->addActor(actor);

- 만약 Actor가 이미 씬에 속해있는 경우 위 코드는 실행되지 않고 무시된다.
- 따라서 Actor를 Aggregate에 추가하고, Aggregate를 씬에 추가하거나, Aggregate를 씬에 추가하고 나서, Actor를 Aggregate에 추가해야 한다.

scene->addAggregate(*aggregate);

 
- 아래는 씬에서 Aggregate를 제거하는 코드다.

scene->removeAggregate(*aggregate);

 
 

Aggregate 해제

PxAggregate* aggregate;    // The aggregate we previously created
aggregate->release();

- PxAggregate를 해제하더라도 묶여있던 Actor들은 해제되지 않는다.
- 만약 PxAggregate가 씬에 속해있다면, Actor들은 자동으로 해당 씬에 '재삽입'된다.
- 만약 PxAggregate와 거기 묶여 있는 Actor들을 모두 삭제하려고 한다면 가장 효율적인 방법은 먼저 Actor들을 해제한 다음에, PxAggregate가 비었을 때 해제하는 것이다.
 


Amortizing 삽입

 
- 여러 개의 객체를 한 프레임 내에 씬에 추가하는 것은 큰 부담을 줄 수 있음
- 이러한 경우로는 랙돌이나, 충돌 검사가 비활성화된 곳에 파편을 예로 들 수 있다.
- 이런 경우 객체 삽입 비용을 여러 개의 프레임으로 분산시키기 위해서는,
파편들을 PxAggregate에 스폰한 다음, 각 Actor를 풀어서 씬에 다시 삽입하면 파편 삽입 비용을 분산시킬 수 있다!
 
 


Trigger Shape
 
- 트리거 Shape는 시뮬레이션에 직접적인 역할을 하지는 않는다.

- 즉, 시뮬레이션 동작에는 영향을 미치지 않지만, 겹치는 다른 Shape와의 상호작용 여부를 보고하는 역할을 한다.
- 이런 Trigger Shape는 위의 SampleSubmarine 예제에서 잠수함이 보물에 도달했는지 여부를 결정하는데 사용된다.
- 보물을 나타내는 PxActor 개체의 유일한 Shape가 Trigger로 세팅되어 있음.

PxShape* treasureShape;
gTreasureActor->getShapes(&treasureShape, 1);
treasureShape->setFlag(PxShapeFlag::eSIMULATION_SHAPE, false);
treasureShape->setFlag(PxShapeFlag::eTRIGGER_SHAPE, true);

 
PxSimulationEventCallback의 자식 클래스인 PxSampleSubmarine 클래스에서
PxSimulationEventCallback::onTrigger의 구현을 통해 SampleSubmarine에서 Trigger Shape와의 겹침을 확인할 수 있다.

void SampleSubmarine::onTrigger(PxTriggerPair* pairs, PxU32 count)
{
    for(PxU32 i=0; i < count; i++)
    {
        // ignore pairs when shapes have been deleted
        if (pairs[i].flags & (PxTriggerPairFlag::eREMOVED_SHAPE_TRIGGER |
            PxTriggerPairFlag::eREMOVED_SHAPE_OTHER))
            continue;

        if ((&pairs[i].otherShape->getActor() == mSubmarineActor) &&
            (&pairs[i].triggerShape->getActor() == gTreasureActor))
        {
            gTreasureFound = true;
        }
    }
}

- onTrigger 함수를 호출하면서, PxTriggerPair 구조체 배열과 그 개수를 인자로 넘겨받음 (pairs, count)
- 함수는 겹치는 Shape pair 배열을 모두 반복해서 보물에 도달했는지 여부를 판단한다.
- 먼저 삭제된 Shape에 대한 처리를 무시하고, 삭제되지 않았다면 Shape pair를 처리한다.
- 만약 겹쳤다면, gTreasureFound를 true로 설정하게 된다.
 
 


Intersection

- PhysX는 브로드 페이즈로 보고된 각각의 겹치는 Shape pair에 대한 Intersection 객체를 내부적으로 생성한다.
- 이 객체들은 강체 쌍 뿐만 아니라 트리거 쌍에 대해서도 생성되는데, 일반적으로 관련 객체 유형과 관련된
PxFilterFlag 플래그와 관계없이 생성된다고 가정해야 함
*레이어로 충돌 그룹을 나눠서 충돌체크여부를 disable로 해놔도, Intersection 객체는 생성된다
 
이 Intersection 객체는 각 Actor마다 65535개의 Intersection 객체를 생성할 수 있다.
(65535 이상의 Intersection이 발생하면 오류메시지를 출력하고 무시된다) 
 

'PhysX > [NVIDIA] PhysX_Tutorial' 카테고리의 다른 글

8. PhysX - RigidBody Dynamics - 2  (0) 2023.03.04
7. PhysX - RigidBody Dynamics - 1  (0) 2023.03.04
5. PhysX - RigidBody 충돌 - 1  (0) 2023.03.03
4. PhysX - RigidBody 개요  (0) 2023.03.03
3. PhysX - Geometry  (0) 2023.03.03

댓글