2015년 5월 3일 일요일

[일기] 기분나쁜 일기장 Springs and Springlike Things (6.2.4 ~ 6.3.1)

6.2.4 A Buoyancy Force Generator ( 부력 힘 생성기 )

  부력은 물체를 뜨게 하는 힘이다. 아르키메데스는 부력이 가라앉은 물체의 부피만큼의 물의 무게와 같다는 사실을 알아냈다.

  그림 [6.3]의 첫번째 부분은 물 안에 한 상자가 가라앉아 있는 것을 나타낸다. 상자는 0.5 kg의 질량을 가진다. 순수한 물은 1,000 kg/m3의 밀도를 가지며 이는 일 세제곱 미터의 물은 약 일 메트릭 톤의 질량을 가짐을 뜻한다. 그림의 상자의 부피는 0.001 m3이며 따라서 정확히 같은 부피만큼의 물을 치환한다. 물체가 차지하고 있는 부피만큼의 물의 질량은 1 kg이 된다.

  물리에서, 무게는 질량과 같지 않다. 질량은 물체가 가속을 저항하는 정도에 대한 속성이다. 그리고 한 물체의 질량은 항상 같다. 무기는 중력에 의하여 물체로부터 발생하는 힘이다. 이미 살펴보았듯이 힘은 다음 다음과 같이 주어진다.

[힘]
[그림6-3]

  f는 무게이며, m은 질량이다. g는 중력 가속도이다. 이 식에 따르면 다른 행성에서 같은 물체는 (질량은 같지만) 행성간 중력가속도가 다르기 때문에 다른 무게가 측정될 것이다.

 지구에서 우리는 중력가속도를 10 m/s2이라 가정한다. 따라서 1 kg의 무게를 갖는 물체는 1 x 10 = 10 kN의 힘에 상응하는 무게를 가진다. 단위 kN은 무게의 단위이다. kilograms, kg은 무게의 단위가 아니다. 이는 우주 프로젝트를 진행하는 과학자들에게 다양한 문제를 안겨준다. 중력이 달라진다면, 그들은 더 이상 파운드와 같은 영어권 단위를 과학 참조 서적에 있는 변환 공식을 이용하여 킬로그램으로 바꾸는 것이 불가능해지기 때문이다. 파운드는 무게의 척도이며, 킬로그램은 질량의 척도이다.

  이제 다시 부력으로 돌아가보자. 우리가 그림 6.3에서 본 첫번째 부분(왼쪽)의 상자는 10 kN만큼의 부력을 받는다. 같은 그림의 두번째 부분(오른쪽)에서는 절반만이 가라앉아 있다. 같은 방식으로 부력을 계산할 경우, 물체는 5 kN의 부력을 받게 된다.

  비록 우리는 힘 생성기를 위해 위 사실을 사용할 필요가 없지만, 물체의 무게를 살펴보는 것은 도움이 된다. 두 경우에서 상자의 무게는 같다(모두 5 kN이다). 그래서 그림의 왼쪽 부분에서 부력은 상자를 위로 올릴것이며 오른쪽 부분에서는 무게가 부력과 같기 때문에 물체는 같은 위치에 머무를 것이다. 즉, 떠 있을 것이다.

  정확하게 물체에게 적용되는 부력을 계산하는 과정은 물에 가라앉아 얼마만큼의 물을 치환하고 있는지를 알기 위해 정확히 어떻게 물체가 생겼는지를 알아내는 과정을 포함한다.  그래야 가라앉은 부피만큼의 물의 무게를 계산할 수 있다. 그러나 만일 다양한 모양의 표류하는 보트의 움직임을 전문적으로 모델링하는 물리엔진을 설계하고자 하는 것이 아니라면, 이러한 상세 수준까지의 구현은 필요하지 않다.

  대신에 우리는 대략적인 추정을 이용하여 스프링과 같은 계산을 할 수 있다. 물체가 표면에 근접해 있다면, 물체에 부력을 적용하기 위해 스프링힘을 사용할 수 있다. 이 힘은 물체의 깊이에 비례할 것이다. 스프링이 줄어들거나 늘어나는 정도에 스프링의 힘이 비례하는 것과 같다. 그림 [6.3]에서 알 수 있듯이, 부분적으로 가라앉은 사각형 상자에서는 오차가 크게 없을 것이다. 다른 형태의 물체에 대해서는 정확하지 않은 부분이 있을 것이나 크게 눈에 거슬린 정도까지는 아닐 것이다. 즉, 무시가능할 정도의 오차가 생긴다.

  만일 상자가 완전히 가라앉는다면, 이때부터는 상자는 다소 다른 방식으로 움직인다. 상자가 물 속으로 더욱 깊이 내려간다 하더라도 물의 부피를 치환하는 양은 변화하지 않는다. 물론, 물의 밀도가 깊이에 따라서도 항상 같다는 가정 하에서 그렇다. 질점을 중심으로 다루는 이 책의 부분에서 물체는 크기가 없으며 따라서 얼마나 그들이 큰지를 알 수 없고, 따라서 물체가 완전히 가라앉았는지 그렇지 않은지를 판단할 수가 없다. 완전히 가라앉은 다음부터는 부력은 더 이상 증가하지 않는다.

  대조적으로 만일 물체가 위로 끌어 올려진다면 물체가 그 높이만큼 들어올려지기 전 까지는 물체의 일부는 필히 물에 잠겨 있게 된다. 이 시점에서는 물체의 수면 밖 부분은 부력을 받지 않는다. 물체에 어떠한 부력도 작용하지 않는 경우에는 물체가 얼마나 높이 있는지는 더 이상 문제가 되지 않는다. 더 이상 물의 부피를 치환하는 부분은 존재하지 않는다는 것만은 확실하다.

 따라서 힘을 계산하기 위한 공식은 다음과 같다.

[식 2]

  s는 침수 깊이(완전히 가라앉은 깊이)이고, p는 유체의 밀도이며, v는 물체의 체적이다. 그리고 d는 물체의 침수 정도로써 완전 침수 깊이에 대한 비율로 주어진다. 즉 완전히 가라앉으면 d >= 1 이고 완전히 수면 밖에 있다면 d <= 0 이 된다.

[식 3]

여기서 y0는 물체의 y 좌표이고 yw는 액체 표면의 높이다(표면이 XZ 평면에 평행하다 가정). 위 공식은 다음과 같이 구현될 수 있다.

[etc]

- pfgen.h 발췌 -
-------------------------------------------------------------------
/**
* @details XZ 평면에 평행한 수면에 대해 부력을 생성하는 힘 발생기
*/
class ParticleBouyancy : public ParticleForceGenerator {
/**
* @ 최대 침수 깊이
*/
real maxDepth;

/**
* 물체의 부피
*/
real volume;

/**
* 수면의 높이 y = 0 이상임. XZ 평면에 평행하다.
*/
real waterHeight;

/**
* 액체의 밀도. 순수한 물의 밀도는 세제곱 미터당 1000 kg이다.
*/
real liquidDensity;

public :
/** 파라미터들을 통해 새 부력 생성기 생성자를 정의 */
ParticleBouyancy(real maxDepth, real volume, real waterHeight, real liquidDensity = 1000.0f);

/** 부력을 입자에 적용한다. */
virtual void updateForce(Particle* particle, real duration);
};
-------------------------------------------------------------------
- pfgen.cpp 발췌 -
-------------------------------------------------------------------
void ParticleBuoyancy::updateForce(Particle* particle, real duration) {
// 침수 깊이 계산 변수
real depth = particle->getPosition().y;

//수면 밖인지를 체크, y0 - yw 가 s보다 크면 0보다 작게 된다. 따라서 떠 있다.
if (depth - waterHeight >= maxDepth) return;
Vector3 force(0, 0, 0);

//다 가라앉았는지 확인. y0 - yw 가 -s 보다 작으면 d 가 1보다 커진다. 따라서 다 가라앉았다.
if (depth - waterHeight <= -maxDepth) {
force.y = liquidDensity * volume;
particle->addForce(force);
return;
}

//부분만 가라앉았다.
force.y = liquidDensity * volume * (depth - maxDepth - waterHeight) / 2 * maxDepth;
particle->addForce(force);
}
-------------------------------------------------------------------

  코드에서 부력은 윗 방향으로 작용한다고 가정하였다. 그리고 물체의 y 위치만을 통해 스프링의 훅의 법칙을 적용한 힘을 계산해 내었다. 이 힘 발생기는 4 개의 마라미터를 통해 생성되는데, 최대 침수 깊이와 물체의 체적, 수면의 높이 그리고 물체가 떠 있는 액체의 밀도에 각각 대응된다. 만일 밀도가 명시되지 않는 경우, 물로 간주되고 물의 밀도를 기본 값으로 하여 인스턴스가 생성된다.

  이 발생기는 단지 한 물체에 대해서만 힘을 적용한다. 이는 발생기 자체가 한 물체의 체적과 크기만을 포함하고 있기 때문이다. 한 인스턴스는 같은 높이의 수면 위에 떠있는, 같은 크기와 부피를 가지는 물체들 사이에서 공유될 수 있지만, 이보다는 각각 물체에 대해 인스턴스들을 별도로 할당하는 편이 오히려 더 좋다.

 6.3 Stiff Springs (고강도 스프링)

  현실세계에서 거의 모든 물체는 스프링과 같이 동작한다. 만일 바위가 땅에 떨어진다면 그 지면은 조금은 초고강도 스프링이 작용하는 것처럼 동작한다. 스프링 동작 모델을 가지고 우리는 모든 것을 시연할 수 있다. 물체간 충돌 또한 부력을 다뤘던 방식과 유사한 방식을 통해 구현될 수 있다. 물체는 서로를 관통할 수 있는데(interpenetration) 이 때 용수철 힘으로 그들을 떼어 놓게 된다.

  각 물체에 정확한 스프링 파라미터를 적용한다면, 위 방법으로 완벽한 충돌을 모델링할 수 있다. 이 방법을 패널티 방법(penalty)이라 한다. 이 방법은 많은 물리 시뮬레이터에서 이용되고 이러한 시뮬레이터 중 몇몇은 게임에서도 쓰인다.

  삶이 단순했다면, 이 책은 200 페이지는 더 짧아졌을 수 있다. 사실 우리는 무력한 스프링 위에서 튕겨지는 모든 물체들이 스펀지처럼 보여지는 현상을 게임에서 피하기 위해 스프링 상수를 매우 크게 잡아야 한다. 만약 그렇게 시도한다면, 엔진을 구동시켜보길 바란다. 머지않아 모든 것이 발광하게 됨을 보게 될 것이다. 물체들은 곧 대부분 즉시 무한으로 사라져 버릴 것이며 당신의 프로그램은 숫자 표현 에러로 인해 붕괴되고 말 것이다. 이것이 고강도 스프링을 구현할 때에 나타나는 대표적인 문제이며 이 때문에 우리의 필요를 생각한다면 패널티 방법은 사용할 수 없을 것처럼 보인다.

  6.3.1 The Problem Of Stiff Springs

  왜 고강도 용수철이 문제를 일으키는지를 이해하기 위해 잠시 스프링이 동작하는 방식을 짧은 시간 단계에 따라 쪼개어 분석해 보도록 하자. 그림 [6.4]는 3~4 회의 시간 진행 절차마다의 용수철의 상태를 보여준다. 첫 단계에서 용수철은 확장되었다. 그리고 이 시점에서 용수철힘이 계산된다.

  이때 생성된 힘은 3장에서 작성한 update 함수에 의해 스프링의 끝에 작용하게 된다.

[식 4]

[그림 6.4]

  다시 말해서, 이 힘은 가속도로 변환된다. - 시간의 한 순간에서 스프링에 한 끝에 작용하는 가속도가 된다. - 이 가속도는 한번 시간 간격 전체 기간동안 물체에 적용된다. 이 사실은 물체가 전혀 움직이지 않는다면 문제가 되지 않는다. 즉, 만약 스프링이 전체 시간 기간동안 계속 일정하게 늘어나 있다면 문제가 되지 않는다.

  실제 세계에서는 스프링이 아주 약간 움직일 때마다, 약간의 시간이 흐르고, 스프링힘은 적게나마 줄어들게 된다. 따라서 전체 기간동안 같은 힘을 일정하게 적용하는 것은 힘이 과도하게 적용되는 꼴이 된다. 그림 [6.4]에서 이 사실은 크게 문제가 되지 않았다. 비록 힘이 과도하게 크더라도, 끝이 다음 시간  프레임 전까지 그리 멀리 이동하지 않았다면 더 낮게 계산된 크기의 힘이 다음 시간 프레임동안 적용되고 하는 과정이 반복될 것이다. 전반적인 효과는 스프링이 무난하게 동작하는 것처럼 나타난다. 그러나 이 때의 스프링 상수는 높게 측정한다고 예상한 수치보다는 낮은 값이다.

  그림 [6.5]가 같은 문제를 보여주지만 이 때의 스프링 상수의 값은 앞서의 것보다 무척 크게 잡혀 있다. 이제 첫번째 프레임 시간동안의 힘은 너무 커서 그 첫 프레임동안에 충분히 물체를 밀어서 평형점(rest length)를 지나고 나아가 스프링을 압축시킬 정도이다. 현실에선 스프링이 이와 같이 동작할 수 없다. 실제로는 이동하면서 지속적으로 (반대 방향의) 큰 힘을 누적해서 받을 것이지만 프로그램 상에서는 그렇지 못하고 다음 프레임이 되었을 때 적용받는 (계산된) 힘의 크기는 급격하게 작아진다.

[그림 6.5]

  그림에서 나타나듯 용수철은 원래 확장되었던 크기보다도 더 많이 압축되어 있다. 다음 시간 프레임에서 물체는 반대 방향까지 이동하게 되나 아까보다도 더 큰 힘을 적용받는다. 따라서 물체는 도를 넘어 움직이고 점점 더 용수철을 확장시킨다. 각각의 프레임에서 용수철은 점점 증가하는 힘과 함께 진동하게 되는데 스프링의 끝이 무한에 도달하게 되면 몸추게 된다. 명확히 말해서 이러한 결과는 옳지 못하다.

  더 긴 시간 간격을 사용할수록 더 쉽게 이러한 현상이 나타난다. 만약 당신의 게임이 스프링을 사용하고 가변 프레임율을 적용한다면 이러한 스프링 상수가 너무 크지 않게 신중을 가해야 할 것이다.  특히 저사양의 기계에서 동작하는 프로그램일 경우에는 더 신중해야 한다. 만약 플레이어가 그래픽 옵션을 변경한다면 그래서 그 사람의 기기가 초당 열개 프레임으로 프레임율이 낮아진다면 이 문제는 발생할 것이다. 아마 당신이 구현한 엔진이 폭주하는 모습은 보고 싶지 않을 것이다.

  우리는 어느 정도는 업데이트 시간 간격을 줄여 힘을 가함으로써 이러한 문제를 해결할 수는 있다. 또는 렌더링하는 각 프레임마다 사소한 정도의 작은 업데이트를 3~4개 추가해 볼수도 있다. 하지만 어떤 것도 그리 만족스럽지는 않다.  현실적인 충돌을 모사하기 위해 사용되는 이러한 스프링의 높은 강도는 사실 우리가 구축하고 있는 프레임워크에서는 가능한 사항이 아니다.

  충돌과 다른 경중 제한을 시연하기 위해 다른 대안을 사용할 것이다.

댓글 없음:

댓글 쓰기