[PhysX] 5화 Material

Grphics 2009. 5. 12. 15:07
이번에 새 키보드를 질러서, 강좌를 쓰는 마음이 상당히 뿌듯하네요. 한 글자씩 쓸 때 마다 밀려오는 감동이란 >_<. 하지만 적응하려면 조금 오래 걸릴 것 같네요.

각설하고, 이번에는 소스보다는 많은 설명이 필요한 부분인 것 같네요. 이번엔 특별히 소스코드는 없고, 소스의 부분만을 설명드리도록 하겠습니다. 참고로, 이것은 NVIDIA PhysX를 설치하면 있는 Training Program 에 있는 Rigid Body > Lesson 105 Material을 기준으로 작성하는 것입니다. 함께 보시면 좀더 이해가 쉬울 것 같네요.

자 시작합니다.

1. 복원력/정지마찰력/운동마찰력

먼저 지난 강좌때 썼던 소스 코드를 열어보세요. 그리고 PhysX.cpp에 InitNx() 함수를 살펴보세요. 그럼 다음과 같은 부분이 있을 겁니다.   
    defaultMaterial->setRestitution(0.5);
    defaultMaterial->setStaticFriction(0.5);
    defaultMaterial->setDynamicFriction(0.5);
오늘은 이 부분을 설명할 겁니다. 근데 생각해보니 이미 1화 Primary Shape에서 이 부분을 언급했더라구요. 그래도 다시 한번 설명드리고 넘어가겠습니다.

먼저 Restitution입니다. t와 i가 참 많이 들어가는 단어네요. 이것은 복원력을 의미하는 단어입니다. 그럼 복원력이란 무엇일까요?

당구공을 생각해 봅시다. 당구공은 이상적인 상황에서는 멈춰있는 공을 빠르게 움직이는 공으로 치는 경우, 움직이던 공은 멈추고 멈추어있던 공운 움직이던 공의 속도로 움직이게 됩니다. 이때 작용하는 것이 복원력입니다. 복원력이 1일 수록, 충돌하였을 때, 속도가 보존됩니다. 0이라면 충돌하자마다 멈추겠죠. 그래서 1일때는 완전 탄성 충돌, 0일때는 완전 비탄성 충돌이라고 불리는 것입니다. 이게 바로 복원력입니다. 더 자세한 공식을 알고 싶으시면, 네X버를 사용하세요~> _<

다음은 Static/Dynamic Friction입니다. 한글로는 정지 마찰력, 운동 마찰력입니다. 이 두개의 개념은 거의 항상 같이 나오는 것으로 다음 그래프를 보면 좀더 이해하기 좋습니다.

그림 1. 정지 마찰력과 운동 마찰력의 관계

정지마찰력은 물체를 움직이기 위한 마찰력을 의미합니다. 예를 들어, 우리가 멈춘 차를 움직인다고 할 때, 처음 움직이기는 힘든데, 움직이기 시작하면 그리 큰 힘이 들지 않는 것을 경험을 통해 알고 있을 겁니다. 물체가 움직이기 시작하면 운동마찰력이 적용됩니다. 많은 경우에서 운동마찰력은 정지마찰력보다 작기 때문에 그런 경험을 할 수 있는 것입니다. 공식으로는 다음과 같습니다.

F = μmg

마찰력은 μ(마찰계수)*m(질량)*g(중력)으로 나타낼 수 있습니다. 여담이지만 면적이 넓다고 마찰력이 큰건 절!대! 아니니 어디가서 무식한 티 내지맙시다. 운동 마찰력은 정지 마찰력 계수보다 작기 때문에 F가 작은 것입니다.

(정지/운동 마찰력은 여기서 복사/붙여넣기 했습니다.)


자 그럼, 이제 실습을 해봅시다. 복원력과 정지마찰력과 운동마찰력의 수치를 바꾸면서 어떤 변화가 있는지 살펴보시기 바랍니다.

복원력이 1에 가까울 수록 위에서 떨어진 박스는 높이 튀어 오르는걸 보실 수 있을 겁니다. 0에 가깝다면 거의 튀어오르지 않겠죠.

정지마찰력은 일단 박스가 가만이 있을때 움직여보시면 알 수 있답니다. 얼마나 움직이기 힘든지를 보시면 됩니다. 잘 감이 안오면 전역변수인 gForceStrength 변수의 값을 낮추어서 실습해 보세요. 박스는 미끄러진다기보다는 모서리로 굴러간다는 느낌이 강할겁니다.

운동마찰력은 박스가 움직이기 시작할 때, 얼마나 빠르게 움직이는지를 보시면 알 수 있습니다.


자 그럼 다음으로 넘어가겠습니다.

2. 재질 조합 모드(Material Combine Modes)

지금까지 우리가 만든 환경은 모두 같은 재질을 가진 물체로만 이루어져있습니다. 만약 두개의 다른 재질로 만든 물체가 충돌한다면, 어떤 물체의 속성을 따라서 물리연산을 해야할까요??

PhysX에는 4가지의 조합모드가 있습니다.
  • NX_CM_AVERAGE : 속성의 평균값
  • NX_CM_MIN : 속성의 최소값
  • NX_CM_MULTIPLY : 속성값의 곱
  • NX_CM_MAX : 속성값의 최대값
기본적으로는 NX_CM_AVERAGE가 설정되어 있습니다. 만약, 다른 조합 모드를 가진 두개의 물체가 부딧치면 어떤 속성이 적용될까요? 여기에는 각 조합모드의 우선순위를 통해 가려집니다. 두개의 다른 조합모드중 우선순위가 높은 모드가 선택됩니다. 그럼 우선순위를 살펴보겠습니다.
  1. NX_CM_AVERAGE : 속성의 평균값
  2. NX_CM_MIN : 속성의 최소값
  3. NX_CM_MULTIPLY : 속성값의 곱
  4. NX_CM_MAX : 속성값의 최대값
위에 적어놓은 순서와 같습니다. 즉 NX_CM_MIN과 NX_CM_MAX를 가진 물체가 부딪치면 NX_CM_MAX의 모드가 적용되는 것입니다. 이런 모드를 설정하고 싶으면 NxMaterial::setFrictionCombineMode() 함수나, NxMaterial::setRestitutionCombineMode()를 이용하면 됩니다.

3. 마찰력 크기 변경하기

게임을 만들다보면, 기상조건에 따라, 마찰력을 다르게 조정해야할 필요가 있을겁니다. 예를 들어, 눈이 오면 아주 미끄러울 것이고, 비가 오면 눈보다는 아니지만 역시 미끄러울 겁니다. 하지만, 이렇게 바뀐다고 해서, 물체 전체의 재질을 변경하기는 힘들죠. 그래서 다음과 같은 옵션이 있습니다.
    gPhysicsSDK->setParameter(NX_DYN_FRICT_SCALING, 0.5);
    gPhysicsSDK->setParameter(NX_STA_FRICT_SCALING, 0.5);
이렇게 설정이 되면 모든 정지/운동 마찰력은 반으로 줄어들게 됩니다. 그래서 기상 조건에 따라 물체의 마찰을 조정할 수 있습니다. 참고로 기본값은 1입니다.

4. Bounce Threshold

gPhysicsSDK->setParameter(NX_BOUNCE_THRESHOLD, -100);

위의 함수는 물체가 튀어오를때, 속도가 100 이하라면 튀어오르게 하지 않는다는 의미입니다. 즉, 농구공과 같이 잘 튀어 오르는 물체를 떨어뜨리면 무한히 튀어오를겁니다. 이때, 속도가 얼마 이하가 되면 더이상 튀어오르지 못하게 하는 값을 설정하는 것입니다. 여기서 속도가 음수인 이유는 상대속도이기 때문입니다.


이번 강좌는 얼룽뚱땅 대충대충 넘어갔네요. 위에 언급했던 함수의 인자들의 속성을 바꾸어가면서 테스트해보세요~ 공부에 많이 도움이 될겁니다.

ps. 물체에 다른 재질을 어떻게 설정하는지 아시는 분은 저에게도 좀... 저도 공부하는 중이라..



posted by 스펜서.

[PhysX] 4화 Actor

Grphics 2009. 5. 11. 14:25
그림 1. 실행화면


오늘만 2번째 강좌를 올리네요. 1화는 내용이 많아서 정말 정리하기가 어려웠는데, 뒤로 갈 수록 짧아지네요~ 정리하는 입장에서는 좋네요~

이번에 배울 내용은 Actor 입니다. Actor는 3D 공간의 충돌 개체를 의미합니다. 여기에는 세가지 종류가 있습니다.
    1. Dynamic
    2. Kinematic
    3. Static
딱 이름만 봐도 대충 알 수 있겠죠? 아니라고요? 사실 저도 아니에요...-_-

Dynamic. 이름이 참 다이나믹하죠? 이 액터는 힘에 의해서 움직일 수도 있고, 충돌도 가능한 객체입니다. 지금까지 우리가 만든 액터들이 모두 Dynamic 액터입니다.

Knematic. 낯선 단어네요. 운동학적인 이라는 뜻이라네요. 이 액터는 한 위치에서 다른 위치로 이동(transfer)는 가능하지만, 힘에 의해서 움직일 수 있는 객체는 아니라고 하네요.
PhysX 도움말에 다음과 같이 나와있네요.
Kinematic actors are special dynamic actors that are not influenced by forces (such as gravity), and have no momentum. They are considered to have infinite mass and can be moved around the world using the moveGlobal*() methods. They will push regular dynamic actors out of the way. Kinematics will not collide with static or other kinematic objects.
Kinematic actors are great for moving platforms or characters, where direct motion control is desired.
모두들 영어는 잘 하실테니.... ㅈㅅㅈㅅ 대충 번역하자면,  다음과 같습니다.
Kinematic 액터는 힘에 영향을 받지 않고, 운동량을 갖지않는 특별한 Dynamic 액터입니다. 무한 질랑을 가진 물체로 간주하며, moveGlobal*() 계열의 함수로 움직일 수 있습니다. 일반적인 Dynamic 액터를 밀 수도 있습니다. 하지만 다른 Static 이나 Kinematic 물체와는 충돌하지 않습니다.  Kinematic 액터는 움직이는 플랫폼이나 캐릭터와 같이 직접적인 조작이 필요한 곳에서 쓰면 좋습니다.
Static. '정적인' 이라는 뜻을 지닌 단어입니다. 이 액터는 어떤 방법을 쓰더라도 움직일 수 없는 녀석입니다. 즉 고정된 물체를 의미합니다. 바닥이나 건물을 생각하시면 좋겠네요.

이 부분도 사실 설명할 부분이 없네요. Kinematic과 Static은 Dynamic 액터를 만드는 방법에서 약간만 바꾸면 됩니다. 박스는 Dynamic, 캡슐은 Kinemaitc, 구는 Static 입니다. Dynamic 함수와 다른 부분을 Kinematic, Static 색으로 표시하겠습니다.

소스 1. Dynamic, Kinematic, Static의 차이
NxActor* CreateDynamicActor()
{
    NxActorDesc actorDesc;
    NxBodyDesc bodyDesc;

    NxBoxShapeDesc boxDesc;
    boxDesc.dimensions = NxVec3(1.0f, 1.0f, 1.0f);
    actorDesc.shapes.push_back(&boxDesc);

    actorDesc.density = 1.0f;
    actorDesc.globalPose.t = NxVec3(6.0f, 1.0f, 0.0f);
    actorDesc.body = &bodyDesc;
   
    NxActor* pActor = gScene->createActor(actorDesc);
    assert(pActor);

    return pActor;
}

NxActor* CreateKinematicsActor()
{
    NxActorDesc actorDesc;
    NxBodyDesc bodyDesc;

    bodyDesc.flags |= NX_BF_KINEMATIC;

    NxCapsuleShapeDesc  capDesc;
    capDesc.radius = 1.0f;
    capDesc.height = 1.5f;
    actorDesc.shapes.push_back(&capDesc);

    actorDesc.density = 1.0f;
    actorDesc.globalPose.t = NxVec3(0.0f, 3.0f, 0.0f);
    actorDesc.body = &bodyDesc;
   
    NxActor* pActor = gScene->createActor(actorDesc);
    assert(pActor);

    return pActor;
}

NxActor* CreateStaticActor()
{
    NxActorDesc actorDesc;
    //NxBodyDesc bodyDesc;

    NxSphereShapeDesc sphereDesc;
    sphereDesc.radius = 1.8f;
    actorDesc.shapes.push_back(&sphereDesc);

    //actorDesc.density = 1.0f; <- static 이기 때문에 무게가 필요없다.
    actorDesc.globalPose.t = NxVec3(-6.0f, 5.0f, 0.0f);
    actorDesc.body = NULL;
   
    NxActor* pActor = gScene->createActor(actorDesc);
    assert(pActor);

    return pActor;
}

소스 뷰어로 보고싶으시면 여길...
posted by 스펜서.

[PhysX] 3화 Compound

Grphics 2009. 5. 11. 12:03
그림 1. 실행 화면

오늘 살펴볼 내용은 여러개의 기본 도형을 혼합하여 사용하는 방법을 알아보겠습니다. 대부분의 경우, 기본적인 도형으로는 충분하지 않습니다. 그래서 위의 그림처럼, 기본 도형이나 삼각형 메쉬를 혼합하여(Compound) 사용합니다. 이번 강좌에서는 지금까지 배운 도형들을 혼합하여 사용하는 방법에 대해서 알아보겠습니다.

아마, 지난 강의부터 궁금해 하던 부분이 있었을 꺼에요. 없나요..?ㅋ 저는 다음 한줄이 너무 궁금했습니다.
actorDesc.shapes.push_back(&sphereShapeDesc);
저는 위에 한줄에서 왜 shapes 일까가 궁금했습니다. 왜 복수일까? 라는 생각을 했었습니다. 결국 이제서야 그 궁금증을 풀게되었네요. ShapeDesc를 만들어서 shapes에 추가만 해주면 됩니다. 참 쉽죠잉~? 죄송합니다(- -) (_ _) (- -)

자 그러면 소스 나갑니다. 이번 소스는 제가 그다지 설명드릴 부분이 없네요. 중요한 부분만 살펴보겠습니다.

소스 1. CreatePrimaryMultiShape() 함수


위에 소스에서 가장 중요한 부분은 이 부분이 아닐까 합니다.

    // 액터의 도형 리스트에 추가
    actorDesc.shapes.push_back(&boxShape);
    actorDesc.shapes.push_back(&capsuleDesc);
    actorDesc.shapes.push_back(&sphereShape);

shapes에 우리가 만든 도형들의 Desc를 추가해주는 겁니다. 아 그리고 한 가지 부분이 더 있네요.
    ...
    boxShape.localPose.t = NxVec3(1.5f, 0.0f, 0.0f);
    ...
    capsuleShape.localPose.t = NxVec3(0.0f, 0.0f, 0.0f);
    ...
    sphereShape.localPose.t = NxVec3(-1.5f, 0.0f, 0.0f);
    ...
바로 이 부분입니다. 로컬 좌표의 위치를 이동시키는 겁니다. 즉, 결과적으로 구성될 메쉬의 중심점으로 부터 이 도형을 어느 곳에 위치시킬 것인지를 정하는 부분입니다. 로컬 좌표와 글로벌 좌표의 관계는 나중에 다시 한번 소개하도록 하겠습니다.

자, 마지막 부분입니다. 이 소스를 마지막으로 강좌를 마쳐야겠네요~

소스 2. CreateMeshMultiShape() 함수


아참, 아마 이렇게 만들어 놓고 돌리면 CreateMeshMultiShape() 함수로 만든 도형은 움직이지 않을꺼에요. 왜냐하면, 너무 무겁기 때문이죠.
NxReal    gForceStrength = 40000;  // 20000에서 40000으로
위와 같이 힘의 세기를 변경하면 될꺼에요. 무거워서 안 움직인거뿐이에요~

posted by 스펜서.

[PhysX] 2화 Mesh Shape

Grphics 2009. 5. 10. 14:49
그림 1. 실행 화면

지난 시간에는 PhysX에서 제공하는 구, 캡슐, 박스, 평면 형태의 기본 모양에 대해서 살펴보았습니다. 이번에 공부할 내용은 임의의 형태를 가지는 충돌개체를 만드는 것입니다. 임의의 형태의 충돌 개체는 기본 도형으로 처리하기 힘든 개체를 표현할 때 사용합니다. 하지만 Mesh-Mesh 충돌검사는 비용이 비싼 계산입니다. 그렇기 때문에 되도록 단순하게 만들어야 합니다.

임의의 형태를 가지는 메쉬를 만드는 데는 두가지 방법이 있습니다. 첫번째는 Convex Mesh를 생성하는 방법입니다. 여기서 Convex는 볼록다각형을 의미합니다. 이에 반대말은 Concave입니다.

그림 2. Convex와 Concave

Concave는 3D 그래픽스에서 많은 단점을 가집니다. 먼저 내부를 색칠해야 하는 문제에 있어서도 Convex 도형에 비해서 알고리즘이 복잡합니다. 그래서 보통은 Concave 도형을 쪼해서 Convex 도형으로 만들어 Convex 도형의 집합으로서 처리합니다.

아무튼 Convex Mesh는 구현하기가 쉽습니다. 왜나하면 정점(Vertex)정보만 넘겨주면 알아서 메쉬를 구성하기 때문이죠. 하지만 이에 상응하는 단점이 있는데요, 그건 정점은 최대 256개를 넘어선 안된다는 겁니다. 즉, 복잡한 Convex Mesh는 만들지 못하는 겁니다.

百聞不如一見, 소스를 봅시다.

소스 1. CreateConvexMesh() 함수

Convex Mesh를 만드는데는 다음과 같은 순서로 생성합니다.
  1. 정점 정보 만들기
  2. NxConvexMeshDesc 객체 생성하기
  3. NxConvexMeshDesc(gPhysicsSDK->createConvexMesh) 객체를 바탕으로 ConvexMesh 생성하기
  4. 생성된 NxConvexMesh(.meshData)와 NxConvexMeshDesc(.userData)를 이용해서 NxConvexMeshShapeDesc 객체 생성하기
  5. NxBodyDesc(.body)와 NxConvexMeshShapeDesc(.shapes)를 이용해서 NxActorDesc 생성하기
  6. NxActorDesc(gScene->createActor)로 Actor만들기
위에서 괄호안에 있는 것은 생성하고 하는 객체에 대응되는 멤버나 함수를 적은 것입니다.

참 쉽죠..?(@_@)

두번째 방법은 Triangle Mesh를 생성하는 방법입니다. 이 방법은 개수의 제한은 없지만, 생성하는 방법이 조금 까다롭습니다. Convex Mesh는 정점 정보만 받았다면, 이 방법은 정점과 인덱스 정보도 주어야 합니다.

잠깐, 정점과 인덱스와의 관계를 설명하겠습니다. 정점(Vertex, 복수는 Vertices)는 말 그대로, 3차원 공간에서의 위치입니다(주로 3D에서의 위치를 Vertex라고 하고, 2D에서는 Point라고 합니다). 인덱스(Index, 복수는 Indices)는 폴리곤의 정보 - 폴리곤은 삼각형, 사각형 등 어떤 기본 도형이든 될 수 있다 - 를 저장하는 개체입니다. 예를 볼까요...?

그림 3. 정점과 인덱스의 관계(폴리곤은 삼각형)

정점을 정의하면 이는 단순히 공간상의 4개의 위치를 의미하는 것입니다. 인덱스는 정의된 정점의 번호를 나열하여, 하나의 다각형을 만드는 것입니다. 위의 예에서 0, 1, 2라고 정의했기 때문에 0, 1, 2번 점을 이어서 하나의 삼각형을 만드는 것입니다. 마찬가지로 3, 0, 2도 해당하는 인덱스에 있는 정점을 이어서 삼각형을 만든 것입니다.

하지만 여기에도 규칙이 있어야 합니다. PhysX에서는 노말을 계산하기 위해서 다음의 공식을 사용합니다.
Normal = (v1 - v0) x (v2 - v0)
즉, 만들고자 하는 면의 방향을 바라본 상태에서, 반시계 방향으로 삼각형을 만들어야 합니다. 그렇지 않으면 Normal이 뒤집혀서 계산될 수 있습니다. 이러면, 제대로된 충돌 검사가 이루어질 수 없죠. 이 부분은 모눈종이에 정점을 적어서 한번 생각해보기시 바랍니다.

그리고, 정점을 넘길 때, 같은 위치를 가리키고 있는 정점을 두개 이상 선언하면 안됩니다. 예를 들어 (0, 0, 0) 위치가 배열에 0번째에 있었는데, 배열의 10번째 위치도 (0, 0, 0)이라면, PhysX는 이러한 중복을 체크하지 않기 때문에 예기치 못한 결과를 야기할 수 있습니다.

자, 그럼 서론은 마무리 짓고, 본격적인 소스를 보겠습니다.

소스 2. CreateTriangleMesh() 함수


기본적인 순서는 Convex Mesh와 매우 유사합니다.
  1. 정점 정보 만들기
  2. 인덱스 정보 만들기
  3. 정점 정보(.numVerices, .pointStridebytes, .points)와 인덱스 정보(.numTriangle, .triangleStrideBytes, .triangles)를 이용해서 NxTriangleMeshDesc 객체 생성하기
  4. NxTriangleMeshDesc(gPhysicsSDK->createTriangleMesh) 객체를 바탕으로 NxTriangleMesh 생성하기
  5. NxTriangleMeshDesc(.userData), NxTriangleMesh(.meshData)를 이용해서 NxTriangleMeshShapeDesc 객체 생성하기
  6. NxTriangleMeshShapeDesc(.shapes)와 NxBodyDesc(.body) 객체를 이용해서 NxActorDesc 생성하기
  7. NxActorDesc(gScene->createActor)를 이용해서 NxActor 생성하기
인덱스는 NxU32 말고 NxU16을 사용할 수 있다. 이럴 경우라면, 폴리곤의 정점이 2^16개를 넘어서지 않는 경우일겁니다. 이럴 때는 위의 소스와 약간 다른데, 그때의 성정하는 방법은 주석으로 적어놓았으니 참고하세요.

자 그럼, 마지막으로 생성한 녀석들을 삽입하기 위해서 InitNx()함수에 추가하겠습니다.
소스 3. InitNx()에 객체 추가하기

setGlobalPosition함수를 이용해서  Triangle Mesh를 (5, 0, 0)으로 이동시킨 코드입니다. 아마 쉽게 이해가 되실 겁니다.

자 이렇게 이제 여러분은 임의의 형태의 충동 객체를 만드실 수 있습니다. 축하합니다. > _<

ps. Triangle이나 Convex Mesh를 생성할 때, 같은 모양의 객체를 여러개 만들고자 할 때는 NxTrangleMeshShape이나 NxConvexMeshShape을 여러개 생성하여 만드시길 바랍니다.



posted by 스펜서.

[PhysX] 1화 Primary Shape #2

Grphics 2009. 4. 28. 21:43

이제!!! PhysX를 본격적으로 살펴보겠습니다. 좀 난이도 높아질 것입니다. 찬물 한잔 하시고... 시작합니다.


가장 먼저 SDK를 초기화 하는 방법부터 살펴보겠습니다.

라이브러리를 초기화하는 방법은 위의 한 줄이면 손쉽게 초기화가 가능합니다. 항상 리턴 값을 확인하도록 합니다.

그 다음으로는 Scene을 생성해야 합니다.


Scene을 생성하기 위해서는 Scene Descripter를 생성하고 이를 인자로 넘겨야 합니다. 여기서는 기본 중력값에 대한 인지와 물리 엔진의 계산 형태를 넘겨줍니다. NX_SIMULATION_SW는 소프트웨어적으로 계산한다는 의미입니다. NX_SIMULATION_HW인 경우, 물리 엔진 계산이 가능한 카드를 이용하여 하드웨어 가속을 받는다는 의미입니다. 일단 카드가 없으니 NX_SIMULATION_SW로 설정하고 생성합니다. PhysX에서는 대부분의 객체를 만들 때, 각각에 해당하는 Descripter가 있습니다. 그 변수에 우리가 원하는 객체의 매개변수를 입력하고 객체를 생성하면 됩니다.

다음은 라이브러리에서 사용할 전역설정을 수행합니다.

NX_SKIN_WIDTH는 물체들의 표면 깊이에 대한 설정입니다. 물체간에 충돌이 일어났을 때, 어느 깊이까지 충돌을 허용하겠냐는 의미입니다. 이번 설정에는 0.01m(1cm)까지는 Overlap을 허용하고 그 이상은 Collision이 일어나는 것입니다..

다음은 디버깅을 위한 파라미터들입니다. NX_VISUALIZATION_SCALE은 충돌 객체와 시각적 객체와의 크기 관계입니다. 1이면 1:1 크기 비율을 사용한다는 의미입니다. NX_VISUALIZE_COLLISION_SHAPES는 충돌하는 객체를 렌더링할 것인가를 의미합니다.
NX_VISUALIZE_ACTOR_AXES는 각 물체의 로컬 좌표계를 화면에 렌더링할 때, 몇개의 축을 렌더링 할 것인가를 의미합니다.



자, 이제 마지막 할 일은 우리가 생성하는 모든 물체에 공통적으로 재질(Material)을 설정하는 것입니다.. 재질(Material)은 충돌에 대한 정의, 표면 속성을 적용하는 것으로, 쉽게 말해, 어떻게 튀고, 미끌어지고, 굴러가는지를 말합니다.

위의 소스는 Scene의 모든 물체는 반발계수 0.5, 정지 마찰력 0.5, 운동마찰력 0.5의 속성을 가진다는 의미입니다. 물리에 해박한 지식은 없지만 각각 옵션을 잠깐 설명하겠습니다. 반발계수는 물체가 다른 물체와 충돌했을때 다시 튀어 나가는 정도를 나타내는 계수로, 출돌 전후의 상대속도의 비로 구할 수 있습니다. 충돌 후 두 물체의 상대 속도가 출돌 전에 비해 얼마나 작아지는지를 나타냅니다. 반발계수가 1이면 완전탄성충돌이라고 부르며, 반발계수가 0이면 완전비탄성충돌이다. 완전탄성충돌은 당구공을 생각하면 좋을듯하고, 완전비탄성충돌은 진흙구슬 두개를 굴려서 부딧혔을때를 생각하면 좋을 것 같습니다. 잠시 정지/운동마찰력을 설명하겠습니다. 이 두개의 개념은 거의 항상 같이 나오는 것으로 다음 그래프를 보면 좀더 이해하기 좋습니다.

그림 1. 정지 마찰력과 운동 마찰력의 관계

정지마찰력은 물체를 움직이기 위한 마찰력을 의미합니다. 예를 들어, 우리가 멈춘 차를 움직인다고 할 때, 처음 움직이기는 힘든데, 움직이기 시작하면 그리 큰 힘이 들지 않는 것을 경험을 통해 알고 있을 겁니다. 물체가 움직이기 시작하면 운동마찰력이 적용됩니다. 많은 경우에서 운동마찰력은 정지마찰력보다 작기 때문에 그런 경험을 할 수 있는 것입니다. 공식으로는 다음과 같습니다.

F = μmg

마찰력은 μ(마찰계수)*m(질량)*g(중력)으로 나타낼 수 있습니다. 여담이지만 면적이 넓다고 마찰력이 큰건 절!대! 아니니 어디가서 무식한 티 내지맙시다. 운동 마찰력은 정지 마찰력 계수보다 작기 때문에 F가 작은 것입니다.

흠. 강의가 약간 옆길로 샌 것 같네요. 저 지긋지긋한 InitNx함수를 이제 마무리 하겠습니다.

Create*함수들은 물체를 생성하는 함수입니다. 각 함수에 대해서는 바로 다음에 알아보도록 하겠습니다. 여기서 gGroundPlane은 바닥을 의미합니다. 3D 공간의 지구라고 생각하면 좋을 것 같습니다.  UpdateTime은 이전 프레임과 현재 프레임의 시간 차를 측정하는 함수 입니다. StartPhysics는 물리 연산을 시작하는 함수 입니다. 본격적인 계산에 앞에서 엔진에 삽입된 물체들의 물리 연산과 시간 차 함수의 초기화를 위해서 한번씩 호출했습니다.




위의 함수는 라이브러리를 해제하는 함수입니다. 비교적 간단하게 해제가 가능합니다. 먼저 현재 엔진에서 계산하는 물리 연산을 마무리 할 수 있도록 대기합니다. 그리고, Scene을 삭제하고, SDK의 메모리를 해제하는 순서로 진행됩니다.

이제 본격적인 엔진 제어 코드가 나옵니다. 심호흡 한번 하고 진행하도록 하겠습니다.

위의 코드는 3D 공간의 물체를 생성하는 함수 입니다. 사실, 물리 연산은 굉장히 복잡합니다. 그래서 복잡한 모형일 수록 계산이 복잡합니다. 이를 빠르게 처리하기 위해서 복잡한 모양의 모델을 단순한 모델로 취급하여 계산합니다. 위의 객체(평면, 육면체, 구, 캡슐)들은 그 때 사용할 수 있는 기본적인 단순한 모델을 말합니다.
먼저 평면을 살펴보겠습니다. 평면은 평면모양의 기본적인 설정을 가지고 있는 Descripter와 충돌에 관여하는 물체라는 의미의 액터(Actor) Descripter를 이용해서 생성합니다.

박스와 캡슐, 구는 생성하는 방식이 비슷합니다. 하지만 평면에는 없는 Body Descripter를 생성하는 것을 볼 수 있는데, 이는 움직일 수 있는 객체를 뜻합니다. 박스는 dimension이라는 멤버를 통해 사이즈르 설정할 수 있습니다. 캡슐, 구도 고유의 멤버를 가지고 있습니다. 한번 찾아보세요~ 쉽게 찾을 수 있을 겁니다.

density는 밀도를 의미하는 것으로, 물체의 무게를 간접적으로 알려줍니다. 물체의 무게는 밀도*부피이기 때문에, 크기가 설정되면 물체의 무게를 알 수 있습니다.

다음은 물리 엔진에서 가장 중요한 프레임 시간 측정 합니다. 이 함수를 정확히 구해야, 정확한 물리 결과가 나옵니다.

QueryPerformance*함수들은 Windows.h에 정의되어 있는 함수입니다. 운영체제에서 제공하는 시간측정함수로 높은 정밀도를 보장하는 함수입니다. static 변수를 이용해서 이전 프레임의 시간과 현재 프레임의 시간을 측정하여 시간차를 구하게 됩니다.

다음으로 물리 엔진의 연산 시작과 끝을 담당하는 함수를 살펴보겠습니다.

StartPhysics함수는 UpdateTime함수를 이용해 물리 연산을 수행하는 부분을 보이고 있습니다. gScene의 멤버 함수는 simulate 함수를 호출하여 시뮬레이션을 시작합니다. 멀티 쓰레드 방식으로 수행되기 때문에 바로 리턴이 됩니다. 시뮬레이션의 종료 여부는 gScene의 멤버인 fetchResults 함수를 이용해서 알 수 있습니다. fetchResults 함수의 두번째 인자가 true이면 블로킹함수로 동작하여 시뮬레이션이 끝날 때까지 리턴하지 않습니다. 만일 fasle이면 넌-블록킹함수로 동작합니다. while 루프에 시뮬레이션과 동시에 수행할 작업(AI, Network 등)을 넣으면 됩니다.

다음은 물체를 제어하는 방법을 설명하겠습니다.

먼저 키조작에 의해 물체에 외력을 가해 움직이게 하는 부분입니다.

내용은 많지만 잘 살펴보면 ProcessForceKey함수만 확인하면 됩니다. ApplyForceToActor 함수는 현재 선택된 액터에 x,y,z축 방향으로 gForceStrength 만큼 외력을 가하는 것입니다. KeyDown과 KeyUp함수는 callback.h/cpp에 있는 KeyboardCallback/KeyboardUpCallback함수에서 호출되는 함수입니다. KeyUp함수에서는 프로그램의 옵션을 설정하는 키들을 설정합니다.

ApplyForceToActor 함수는 다음과 같습니다.

방향과 크기, 그리고 시간 차를 이용해서 외력벡터(forceVec)을 만듭니다. 그리고 이를 액터의 addForce 함수를 이용하여 외력의 합을 구합니다.

자 이제 남은 부분은 부수적인 부분이 남았군요.. 힘내서 마저 포스팅 해보겠습니다.

다음은 r키를 누르때 동작하는 액터 바꾸기 입니다.

IsSelectable함수는 인자로 넘어온 액터가 움직일 수 있는 액터인지를 확인하는 함수입니다. NX_TRIGGER_ENABLE 인자가 있거나 static 설정이 되어 있는 액터는 움직일 수 없기 때문에 이를 체크하는 함수입니다. SelectNextActor함수는 IsSelectable함수를 이용하여 선택가능한 다음 물체를 선택하는 함수입니다. 선택된 액터는 키조작에 의한 외력의 영향을 받습니다.

위의 함수는 렌더링과 관련된 함수입니다. RenderActors 함수는 Scene에 있는 모든 액터를 화면에 렌더링하는 역할을 합니다. 함수이름에 "Nb"가 있으면 개수를 가져오는 함수임을 알 수 있습니다. DrawForce 함수는 물체에 가해지는 외력의 모습을 렌더링합니다.
제일 중요한 함수가 RenderScene입니다. 렌더링하기 전, 이전 프레임의 물리 연산 결과를 받습니다. 그 다음 사용자의 입력을 수행하고, 입력에 대한 물리 연산을 수행합니다. 그 사이에 이전 프레임에서 행했던 물리 연산의 결과를 화면에 렌더링하는 순서를 가지고 있습니다.
최근에는 게임 엔진들이 멀티 쓰레딩이 기본이라, 이런식으로 한 작업이 수행될 때 다른 작업을 수행할 수 있도록 하는 구조가 보편적으로 사용된다고 합니다.


프로그램 수행에 필요한 코드를 모두 설명했습니다. 처음하는 지라, 순서가 뒤죽박죽인 듯한 느낌이 강하네요. 차츰 더 나아 질 겁니다. 다음은 실행 결과입니다.

그림 2. 실행 결과

r : 액터 선택하기
p : 멈추기
x : 그림자
b : 디버깅 Wireframe 그리기
wasdqz : 카메라 움직이기
ikjlum : 물체 움직이기


다음은 이번 강좌에서 사용한 주요 함수, 클래스들입니다. 천천히 읽어보면서 기억을 상기하는 것도 괜찮을 것 같습니다.


소스코드는 이전 포스트에 올려놓았습니다. nVidia에서 제공하는 소스와 살짝 다릅니다.(사실 귀찮은 기능들은 다 빼버린 지라... 특히 HUD 부분이 빠져있습니다.) 기본적으로는 거의 같은 소스입니다. 참고하세요~^^
posted by 스펜서.

[PhysX] 1화 Primary Shape #1

Grphics 2009. 4. 28. 20:13
지난주부터 PhysX를 공부하면서 정리하려고 했는데, NVIDIA에서 제공하는 튜토리얼이 있긴 하지만 정리하려고 했더니 만만치 않네요. 특히 PhysX는 물리 엔진이기 때문에 렌더링과 관련 라이브러리가 없답니다. 그래서 초반에 셋팅해야 하는 것들이 만만치 않게 많아요.

이번 포스트는 nVidia에서 제공하는 튜토리얼을 기반으로 하고 있습니다. 다만, 단순 번역이 아니라 소스 코드의 순서나, 설명을 제 나름대로 재구성해보았습니다.

만만치 않은 양이지만, "시작이 반이다"라는 말이 있듯이, 한번 달려보죠. 아자!


아시다시피 PhysX는 물리엔진입니다. 그렇기 때문에, 화면에 출력하는 그래픽과 관련된 API가 전무합니다. 사실 그래야 물리엔진이겠죠. 그래서 PhysX를 사용하기 위해서는 렌더링을 할 수 있는 API를 사용해야 합니다. 저는 OpenGL을 사용하도록 하겠습니다. DX도 가능하지만, DX 보다는 OpenGL이 간단하게 프로그래밍하기 좋기 때문에, OpenGL을 사용하도록 하겠습니다.

OpenGL에는 GLUT라는 라이브러리가 있습니다. 이 라이브러리는 DXUT와 마찬가지로 손쉽게 OpenGL을 코딩할 수 있도록 해주는 프레임워크 라이브러리입니다. 이 라이브러리가 없으신 분은 다운 받아 설치합시다.

혹시나 PhysX를 설치하지 않으신 분들은 여기에 강좌가 있으니 설치를 먼저 하세요.

강좌에 사용된 소스코드입니다. 같이 보시면서 공부하시면 도움이 될 것 같습니다. nVidia에 있는 소스와 약간 다릅니다.


이 소스는 기본적으로 PhysX와 관련된 폴더가 환경설정에 추가가 되어 있어야 합니다. 포함파일(include)폴더들과 라이브러리파일(lib) 폴더 경로가 설정되어 있어야 합니다. 또 "PhysX공용"이라고 되어 있는 곳의 소스는
  • (NVIDIA PhysX)\v2.8.1\TrainingPrograms\Programs\Shared_Source
  • (NVIDIA PhysX)\v2.8.1\TrainingPrograms\Programs\Shared_Source\User_Reports
에 있는 소스입니다. 참고하시기 바랍니다.


자 이제 시작하겠습니다. 먼저 메인 함수부터 만들어봅시다.

callback.h에는 주로 렌더링에 필요한 기반 코드와 카메라 제어를 위한 마우스, 키보드 코드가 있니다. physx.h에는 PhysX 라이브러리를 사용하는 물리 엔진 함수와 객체를 렌더링하는 코드, 객체를 제어하기 위한 키보드 입력을 위한 코드가 있습니다.

먼저, InitGLUT함수를 이용해서 렌더링을 위한 윈도우 생성과 OpenGL 초기화를 할 예정입니다. InitNx함수에서는 PhysX의 초기화를 진행하겠습니다. RunGLUT함수가 실행되면 루프를 돌면서 이벤트 처리, 물리 연산, 렌더링이 진행됩니다. 프로그램이 종료되면 ReleaseNx함수가 호출되면서 PhysX에서 사용하던 메모리 해제가 진행됩니다.

1. InitGLUT함수에서는 GLUT에서 필요한 콜백함수를 등록합니다. glutInit함수를 통해 GLUT 라이브러리를 초기화합니다.
2. glutInit*()함수들을 이용해서 우리가 생성하고자 하는 윈도우의 렌더링모드를 설정하고 장의 위치 및 크기를 설정합니다.  마지막으로 glutCreateWindow함수를 이용해서 창을 생성합니다.
3. GLUT에서 필요한 콜백함수를 등록하여 우리가 구현한 함수가 해당하는 이벤트가 발생할 때 실행될 수 있도록 합니다.

먼저 glutReshapeFunc 함수에 등록한 ReshapeCallback 함수부터 구현해봅니다. WM_SIZE가 발생할 때 실행되는 함수 입니다.  먼저 전역변수로 gWindowWidth, gWindowHeight를 만듭니다.  이 변수들은 나중에 카메라 설정때 Aspect Ratio를 구하기 위해서 사용됩니다. 이 함수에서는 창의 크기가 바뀔 때 마다 창에 꽉 차게 렌더링을 하기 위해서 Viewport를 설정합니다.  Viewport를 생성하고 카메라와 조명을 셋팅합니다.


ApplyLight함수는 조명을 설정합니다. 조명은 방향성 조명으로 설정했습니다. glColorMaterial 함수를 이용해서 메쉬에 적용된 색이 Ambient, Diffuse Light에 반응하도록 했습니다. ApplyCamera함수는 카메라을 설정합니다. 전역변수로 설정되어 있는 gCamera* 변수를 이용해서 pos, look, up 벡터를 설정합니다.

이제 좀 더 어려운 부분인 카메라 제어 부분을 살펴보겠습니다. 카메라 제어를 위해서 키보드 관련 콜백함수와 마우스 콜백 함수를 사용합니다.

이 부분이 조금 헷깔릴 수 있습니다. gKeys 배열은 physx.cpp에 정의되어 있는 전역변수입니다. 이를 callback.h에서 사용하기 위해서 physx.h에서 extern으로 전역변수 선언을 한 후, callback.cpp에서 사용한 것입니다. 사실 한 파일에 몽땅 코딩해 버리면 이렇게 할 필요가 없지만, 좀 정리하면서 코딩을 하기 위해서 분리해 놓은 것입니다. 여러분은 extern을 이해할 수 있을 거라 보고, 설명은 넘어가겠습니다. 
KeyboardCallback함수에서는 키보드를 눌렀을 때 호출되는 함수입니다. 이 함수가 호출되면 해당하는 키가 눌렸다는 것을 gKeys배열에 표시합니다. 만약 esc키를 누르면, 프로그램을 종료합니다. KeyDown함수는 physx..h/cpp에 선언된 함수로 PhysX에서 키를 눌렀을때, 하고 싶은 이벤트를 구현한 함수입니다.
KeyboardUpCallback함수는 키를 땠을 때 호출되는 함수입니다. 키를 땠을 경우, gKeys 배열에 이를 표시합니다.

ProcessCameraKey함수에서는 gKeys 배열에 기록된 내용을 토대로 카메라의 위치를 이동합니다. 여기서 gCameraSpeed는 카메라의 움직임이 얼마나 빠르게 동작하는지를 의미합니다. deltaTime은 이전 프레임과 현재 프레임의 시간 차를 의미합니다. 정확하게 코딩하려면 시간 차를 구해서 적용해야 하지만, 이번 강좌에서는 약식으로 넘어가도록 하겠습니다.

카메라 제어 코드의 도식(w를 눌렀을 경우)

그림 1. 카메라 변수들 간의 관계

위의 그림은 ProcessCameraKey 함수에서 사용된 공식을 그림으로 나타낸 것입니다. 카메라의 위치는 gCameraPos이고, w를 눌렀을 경우, gCameaForward 방향으로 deltaTime*gCameraSpeed만큼 이동하게 됩니다. s를 누르게 되면 그 반대로 이동하게 하면 될 것입니다. a와 d는 gCameraForward대신 gCameraRight를 적용하면 좌우로 움직일 수 입니다.

다음은 마우스 쪽의 코드를 살펴보겠습니다.

MouseCallback함수는 마우스를 클릭한 지점을 전역변수인 mx, my에 저장합니다. MotionCallback함수는 드래깅 할 때 호출됩니다. 드래깅할 때 mx, my에서 x, y를 빼서, 클릭한 지점으로 부터 얼마만큼 떨어져있는지 dx, dy를 구합니다. 다음 계산을 위해서 gCameraForward를 정규화하고, gCameraRight를 gCameraForward와 y축을 외적하여 구합니다. 그리고 dx, dy를 각으로 변환하여 y축으로 dx만큼 회전하는 쿼터니온과, gCameraRight를 축으로 dy만큼 회전하는 쿼터니온을 생성합니다. 이를 이용해서 gCameraForward를 회전변환합니다. 쿼터니온은 그래픽스에서 자주 나오는 개념이니까 꼭 공부해보시기 바랍니다.

이제 callback.cpp는 거의 다 끝났습니다. 휴.. 남아 있는 함수를 보도록 하겠습니다.

void RenderCallback함수는 렌더링을 해야 할때 호출되는 함수이며, IdleCallback 함수는 렌더링이 끝난 후에 호출되는 함수 입니다. 사실 GLUT에서는 게으른 방식(??)으로 렌더링을 합니다. 즉, 렌더링되는 윈도우가 움직인다거나, 다시 그려야하는 경우, WM_PAINT가 호출되는 시점과 같은 시기에 렌더링이 됩니다. 즉, 매번 렌더링이 되는게 아니랍니다. 그래서 IdleCallback 함수에서 glutPostRedisplay()함수를 호출하여 다시 렌더링을 하도록 합니다. 이 함수는 마치 Invalidate()함수와 비슷합니다.

RenderCallback 함수에서는 glClear함수를 이용해 그래픽 버퍼를 초기화하고, 렌더링 하기 전, 카메라 제어에 대한 처리를 수행합니다(ProcessCameraKey(), ApplyCamera()).
그리고, PhysX의 객체 렌더링을 수행하는 함수인 RenderScene(physx.h/cpp)를 호출하고, glFlush함수로 큐에 대기중인 모든 OpenGL 명령을 그래픽카드로 보내고, glutSwapBuffers함수가 그래픽 버퍼를 교체합니다(더블 버퍼링, glutInitDisplayMode함수의 GLUT_DOUBLE인자가 더블버퍼링을 위한 인자입니다.).

휴... GLUT와 관련된 함수를 살펴보았습니다. 아마 당분간은 이 부분은 다시 살펴보지 않아도 될 것입니다. 초기화가 대부분이고 PhysX에 의해 바뀔 가능성이 있는 부분은 모두 함수로 빼서 physx.h/cpp에 정의 했으니까 callback.h/cpp는 가끔 살피면 될 것 같습니다.



포스트가 너무 길어졌네요. 다음 physX와 관련된 함수는 다음 포스트에서 이어가겠습니다.

"[PhysX] 1화 Primary Shape #2" 보기
posted by 스펜서.

OpenGL Perspective 함수

Grphics 2009. 4. 20. 00:57


두 함수는 같은 역할을 하는데 입력받는 인자의 모양이 틀리다. glFrustum 함수로 gluPerspective 함수를 구현다려면 다음과 같이 해야한다.

posted by 스펜서.

OpenGL과 Direct 텍스쳐 좌표계

Grphics 2009. 4. 20. 00:36

그림1. OpenGL의 텍스쳐 좌표계

맨날 까먹는 OpenGL의 Texture Coordination.
DirectX는 이것과 상하가 바뀌어 있다. 약간의 코드만 넣으면 OpenGL의 좌표계를 다이렉트 X의 좌표계로 변환이 가능하다.

posted by 스펜서.