Skip to content
cfd-lab:~/ko/posts/2026-08-13-metacentric-h…online
NOTE #130DAY THU 유체역학DATE 2026.08.13READ 6 min read#Metacenter#Buoyancy#Free-Surface#Fluid-Mechanics#FSI

무게중심이 위에 있는데도 안 뒤집힌다 — 메타센터 GM과 자유표면 효과

GM은 수선면 관성모멘트가 벌어 주는 여유다. 탱크에 담긴 물의 자유표면 하나가 그 여유를 통째로 가져갈 수 있다.

컨테이너선의 무게중심은 물에 잠긴 부분의 중심보다 몇 미터 위에 있다. 무거운 쪽이 위에 얹혀 있는데도 배는 스스로 일어선다. 반대로 무게중심이 부력중심보다 아래인데 뒤집히는 부선(barge)도 있다. 위아래 관계만으로는 아무것도 결정되지 않는다는 뜻이다. 이 글은 그 판정을 실제로 내리는 양 — 메타센터 높이 GMGM — 이 어디서 나오는지, 그리고 탱크에 담긴 물이 왜 그 값을 통째로 지워버리는지를 다룬다.

무게중심이 부력중심보다 위에 있다#

기호부터 맞추자. KK는 용골(keel, 배 바닥 중심), GG는 무게중심, BB는 부력중심이다. 부력중심은 물에 잠긴 부피의 도심(centroid)이다. 아르키메데스가 말한 것은 크기뿐이다. 부력의 크기는 밀어낸 물의 무게와 같고, 작용선은 잠긴 부피의 도심을 지난다.

똑바로 떠 있을 때 GGBB는 같은 연직선 위에 있다. 무게와 부력은 크기가 같고 방향이 반대라 모멘트가 0이다. 이 상태에서는 GG가 위든 아래든 아무 일도 일어나지 않는다. 안정성은 정지 상태가 아니라 살짝 기울였을 때 무엇이 생기는가로 정의된다.

기울이면 사정이 달라진다. BB는 잠긴 모양의 도심이므로 잠긴 모양이 바뀌면 함께 옮겨 간다. GG는 배와 함께 회전할 뿐 배 안에서는 제자리다. 두 연직선이 어긋나면서 모멘트 팔이 생긴다. 이것을 복원정(righting arm) GZGZ라 부르고, 복원 모멘트는 배수량 Δ\Delta에 대해 M=ΔGZM = \Delta \cdot GZ다.

기울면 잠긴 모양이 바뀐다#

아래 시뮬레이션에서 직접 기울여보자. 폭·흘수·무게중심을 바꾸고 release at 15 deg를 눌러 돌아오는지 보면 된다.

phi = 15.0 deg
Raise KG past 7.33 m and the barge no longer comes back — it rolls out to an angle of loll and stays there. Hold the heel and drag it past 27 deg: the deck edge goes under, the waterplane stops widening, and the green GZ curve peels away from the dashed straight line GM sin phi. That gap is why a single number cannot certify a hull.

BB(초록)가 기울인 쪽으로 끌려가고, GG(노랑)를 지나는 연직선과 벌어지는 간격이 GZGZ다. 보라색 MM은 부력 작용선이 배 중심선과 만나는 점이다. KG 슬라이더를 7.33 m 위로 올리면 MMGG 아래로 내려가고, 배는 돌아오는 대신 한쪽으로 누워 멈춘다.

BM=I/BM = I/\nabla — 수선면의 관성모멘트가 전부다#

작은 각도 ϕ\phi에서 잠긴 모양의 변화는 쐐기 두 개로 정리된다. 한쪽 현측에서 물 밖으로 나온 쐐기와 반대쪽에서 새로 잠긴 쐐기다. 두 쐐기의 부피는 같고(배수량이 그대로여야 하므로) 좌우로 떨어져 있다. 이 부피 이동이 BB를 옮긴다.

수선면(waterplane, 수면이 배를 자른 단면)의 미소 띠 dAdA가 중심선에서 xx만큼 떨어져 있으면, 그 띠가 만드는 부피 변화는 xϕdAx\phi\,dA이고 모멘트는 x2ϕdAx^2\phi\,dA다. 전부 더하면 관성모멘트가 나온다.

BB=ϕAx2dA=Iϕ,BM=I\overline{BB'} = \frac{\phi \int_A x^{2}\,dA}{\nabla} = \frac{I\,\phi}{\nabla}, \qquad BM = \frac{I}{\nabla}

II는 중심선에 대한 수선면의 2차 관성모멘트, \nabla는 배수 부피다. 폭 bb인 직사각형 단면이라면 단위 길이당 I=b3/12I = b^3/12, =bT\nabla = b\,T이므로

BM=b212TBM = \frac{b^{2}}{12\,T}

가 된다. 여기서 폭이 세제곱으로 들어갔다가 분모의 \nabla에서 한 번 상쇄되어 제곱으로 남는다는 점이 중요하다. 폭을 20% 넓히면 BMBM은 44% 커진다. 카누가 불안정하고 뗏목이 안정한 이유가 여기 있다. 그리고 판정식은 한 줄로 끝난다.

GM=KB+BMKGGM = KB + BM - KG

KBKB는 부력중심 높이, KGKG는 무게중심 높이다. GM>0GM > 0이면 복원, GM<0GM < 0이면 전복이다. 폭 b=16b = 16 m, 흘수 T=4T = 4 m인 부선이면 KB=2.0KB = 2.0 m, BM=5.333BM = 5.333 m이므로 KM=7.333KM = 7.333 m다. KGKG가 6.6 m라면 여유는 0.733 m뿐이다.

GMsinϕGM \sin\phi가 배신하는 각도#

GMGMϕ0\phi \to 0의 기울기다. 작은 각도에서는 GZGMsinϕGZ \approx GM \sin\phi가 잘 맞는다. 문제는 이 근사가 언제 깨지는지다.

깨지는 지점은 기하학이 정한다. 수선면이 변하지 않는 동안만 II가 상수이기 때문이다. 위 부선은 건현(freeboard, 수면 위 선체 높이)이 4 m이므로 tanϕ=2×4/16\tan\phi = 2 \times 4/16, 즉 26.6°에서 갑판 끝이 물에 잠긴다. 그 순간부터 수선면은 더 넓어지지 못하고 오히려 줄어든다. BMBM이 무너지고 GZGZ 곡선은 꺾인다.

위 시뮬레이션에서 hold heel을 누르고 각도를 30° 너머로 밀어보면 초록 실선(정확값)이 보라색 점선(GMsinϕGM\sin\phi)에서 떨어져 나가는 것이 보인다. 두 곡선의 부호가 갈리는 각도가 곧 소멸각이다.

탱크 속 물이 GM을 통째로 가져간다#

이제 배 안에 액체가 담긴 탱크를 넣자. 밸러스트 탱크, 연료 탱크, 화물 탱크, 소화용수로 잠긴 자동차 갑판까지 전부 같은 문제다.

액체의 무게는 기울여도 변하지 않는다. 옮겨 갈 뿐이다. 그런데 그 이동이 GG를 아래쪽 현측으로 끌고 간다. 계산 구조는 BMBM과 똑같다. 탱크 안 자유표면도 하나의 수선면이고, 기울면 쐐기 두 개가 자리를 바꾼다.

δGM=ρfiρ\delta GM = \frac{\rho_f\, i}{\rho\, \nabla}

ii는 탱크 자유표면의 관성모멘트, ρf\rho_f는 탱크 액체 밀도, ρ\rho는 바깥 물의 밀도다. 이 값은 배에서 실제로 빼는 것이 아니라 GG가 그만큼 위로 올라간 것처럼 취급한다(가상 상승, virtual rise). 탱크에 물이 얼마나 들어 있는지는 식에 없다. 넘칠 듯 가득 차 자유표면이 없어지거나 완전히 비지 않는 한, 20 cm가 담겼든 2 m가 담겼든 손실은 같다.

아래에서 칸막이 슬라이더를 움직여보자.

phi = 10.0 deg
Start at n = 1 and press release: the liquid slides to the low side and the barge never comes back. Now drag the bulkhead slider. The liquid volume never changes, the centre of gravity of the ship at rest never changes — only the width of each free surface does, and at n = 2 the red loss bar has already dropped to a quarter.

노란색이 탱크 액체다. 액체 표면은 선체가 어떻게 기울든 수평을 유지한다. n = 1에서 놓으면 배는 돌아오지 않고 한쪽으로 누워 버틴다(각도 부동, angle of loll). 칸막이를 하나 세우는 순간 빨간 손실 막대가 1/4로 줄고 배가 일어선다.

칸막이 하나가 손실을 1/4로 줄인다#

btb_t인 탱크를 nn개로 나누면 각 칸의 폭은 bt/nb_t/n이고 칸이 nn개다.

i=n(bt/n)312=bt312n2i = n \cdot \frac{(b_t/n)^{3}}{12} = \frac{b_t^{3}}{12\,n^{2}}

손실은 1/n1/n이 아니라 1/n21/n^2로 줄어든다. 폭의 세제곱이 개수의 1제곱을 이기기 때문이다. 위 부선에 폭 12 m 탱크가 하나 있으면 i=144i = 144 m⁴, =64\nabla = 64 m³/m이므로 δGM=2.195\delta GM = 2.195 m다. GMGM 0.733 m는 이미 한참 전에 사라졌다. 중앙에 칸막이 하나만 세우면 0.549 m로 떨어지고 배는 다시 선다.

이것이 유조선 중앙에 종격벽이 서 있는 이유다. 화물량도, 무게중심 높이도 바뀌지 않는다. 자유표면의 폭만 반이 된다.

Python으로 그려본 GZ 곡선과 소멸각#

GMGM은 접선의 기울기 하나일 뿐이다. 큰 각도의 GZGZ는 잠긴 다각형을 직접 잘라서 도심을 구하는 편이 빠르다. 선체 단면을 다각형으로 놓고, 잠긴 면적이 배수 면적과 같아지는 수선 높이를 이분법으로 찾은 뒤, 그 다각형의 도심을 계산한다.

import math
 
def poly_area_centroid(poly):
    a = cx = cy = 0.0
    n = len(poly)
    for i in range(n):
        x0, y0 = poly[i]
        x1, y1 = poly[(i + 1) % n]
        cr = x0 * y1 - x1 * y0
        a += cr
        cx += (x0 + x1) * cr
        cy += (y0 + y1) * cr
    a *= 0.5
    if abs(a) < 1e-14:
        return 0.0, 0.0, 0.0
    return a, cx / (6 * a), cy / (6 * a)
 
def clip_below(poly, phi, h):
    """heel phi 상태에서 지구 기준 높이가 h 이하인 부분만 남긴다"""
    def f(p):
        return p[1] * math.cos(phi) - p[0] * math.sin(phi) - h
    out = []
    n = len(poly)
    for i in range(n):
        p, q = poly[i], poly[(i + 1) % n]
        fp, fq = f(p), f(q)
        if fp <= 0.0:
            out.append(p)
        if (fp < 0.0) != (fq < 0.0):
            t = fp / (fp - fq)
            out.append((p[0] + t * (q[0] - p[0]), p[1] + t * (q[1] - p[1])))
    return out
 
def waterline_offset(poly, phi, area0):
    """잠긴 면적이 area0 가 되도록 수선 높이를 이분법으로 찾는다"""
    lo, hi = -60.0, 60.0
    for _ in range(80):
        mid = 0.5 * (lo + hi)
        a, _, _ = poly_area_centroid(clip_below(poly, phi, mid))
        if a < area0:
            lo = mid
        else:
            hi = mid
    return 0.5 * (lo + hi)
 
def righting_arm(beam, depth, draft, kg, phi):
    hull = [(-beam / 2, 0.0), (beam / 2, 0.0), (beam / 2, depth), (-beam / 2, depth)]
    h = waterline_offset(hull, phi, beam * draft)
    _, xb, yb = poly_area_centroid(clip_below(hull, phi, h))
    return xb * math.cos(phi) + (yb - kg) * math.sin(phi), xb, yb
 
def gz_curve(beam, depth, draft, kg, deg_max=70, step=1):
    return [(d, righting_arm(beam, depth, draft, kg, math.radians(d))[0])
            for d in range(0, deg_max + 1, step)]
 
BEAM, DEPTH, DRAFT = 16.0, 8.0, 4.0
KG = 6.6
KB = DRAFT / 2
BM = BEAM ** 2 / (12 * DRAFT)
GM = KB + BM - KG
print(f"KB={KB:.3f} m  BM=B^2/12T={BM:.3f} m  KM={KB+BM:.3f} m  KG={KG:.3f} m  GM={GM:.3f} m")
print(f"deck edge immerses at phi = {math.degrees(math.atan(2*(DEPTH-DRAFT)/BEAM)):.1f} deg")
print()
print(" phi[deg]   GZ[m]   GM*sin(phi)[m]   error[%]")
for d, gz in gz_curve(BEAM, DEPTH, DRAFT, KG, 60, 5):
    lin = GM * math.sin(math.radians(d))
    err = f"{100 * (lin - gz) / gz:8.1f}" if abs(gz) > 0.01 else "       -"
    print(f"  {d:5.1f}   {gz:7.4f}   {lin:11.4f}   {err}")
 
curve = gz_curve(BEAM, DEPTH, DRAFT, KG, 70, 1)
dmax, gzmax = max(curve, key=lambda t: t[1])
vanish = next((d for d, g in curve if d > 5 and g <= 0.0), None)
print()
print(f"max GZ = {gzmax:.3f} m at {dmax} deg,  vanishing stability at {vanish} deg")
KB=2.000 m  BM=B^2/12T=5.333 m  KM=7.333 m  KG=6.600 m  GM=0.733 m
deck edge immerses at phi = 26.6 deg
 
 phi[deg]   GZ[m]   GM*sin(phi)[m]   error[%]
    0.0    0.0000        0.0000          -
    5.0    0.0657        0.0639       -2.7
   10.0    0.1417        0.1273      -10.2
   15.0    0.2394        0.1898      -20.7
   20.0    0.3716        0.2508      -32.5
   25.0    0.5550        0.3099      -44.2
   30.0    0.7207        0.3667      -49.1
   35.0    0.6823        0.4206      -38.4
   40.0    0.5196        0.4714       -9.3
   45.0    0.2828        0.5185       83.3
   50.0    0.0001        0.5618          -
   55.0   -0.3116        0.6007     -292.8
   60.0   -0.6406        0.6351     -199.1
 
max GZ = 0.727 m at 31 deg,  vanishing stability at 51 deg

5°에서 오차는 2.7%다. 30°에서는 GMsinϕGM\sin\phi가 실제 GZGZ의 절반밖에 안 된다. 갑판이 잠기기 전까지 수선면이 계속 넓어져 실제 복원력이 선형 예측보다 크기 때문이다. 그러다 45°를 넘으면 부호가 뒤집혀 이번에는 선형 근사가 안전 쪽으로 거짓말을 한다. 50°에서 실제 GZGZ는 0인데 GMsinϕGM\sin\phi는 여전히 0.56 m를 약속한다.

자유표면 손실은 다각형을 자르지 않고도 확인할 수 있다. 갑판이 잠기기 전까지는 현측이 수직이라 복원정에 닫힌 식이 있기 때문이다. 손실은 가상 상승이므로 GMGM에서 δGM\delta GM을 빼고 넣으면 된다.

import math
 
BEAM, DRAFT, KG = 16.0, 4.0, 6.6
NABLA = BEAM * DRAFT
BM = BEAM ** 2 / (12 * DRAFT)
GM = DRAFT / 2 + BM - KG
 
def free_surface_loss(rho_f, rho, b, n, nabla):
    """칸막이 n개로 나뉜 폭 b 탱크의 자유표면 손실 [m]"""
    i_free = n * (b / n) ** 3 / 12
    return rho_f * i_free / (rho * nabla)
 
def wall_sided_gz(gm, bm, phi):
    """갑판이 잠기기 전, 현측이 수직인 구간의 복원정"""
    return (gm + 0.5 * bm * math.tan(phi) ** 2) * math.sin(phi)
 
print(" tanks   i[m^4]   dGM[m]   GM_eff[m]   GZ(20deg)[m]")
for n in (1, 2, 3, 4):
    d_gm = free_surface_loss(1000.0, 1025.0, 12.0, n, NABLA)
    gz20 = wall_sided_gz(GM - d_gm, BM, math.radians(20))
    print(f"  {n:4d}  {n*(12.0/n)**3/12:7.1f}  {d_gm:7.3f}   {GM-d_gm:8.3f}   {gz20:11.3f}")
 tanks   i[m^4]   dGM[m]   GM_eff[m]   GZ(20deg)[m]
     1    144.0    2.195     -1.462        -0.379
     2     36.0    0.549      0.185         0.184
     3     16.0    0.244      0.489         0.288
     4      9.0    0.137      0.596         0.325

칸막이 하나로 ii가 144에서 36으로, 정확히 1/4이 된다. 20°에서의 복원정은 −0.379 m에서 +0.184 m로 부호가 바뀐다. 여기 쓴 닫힌 식 GZ=(GM+12BMtan2ϕ)sinϕGZ = (GM + \tfrac{1}{2}BM\tan^2\phi)\sin\phi는 갑판이 잠기기 전 구간에서 다각형 계산과 일치한다. 손실이 없을 때 20°의 값 0.3716 m가 위 표와 같다.

6-DOF VOF 해석에 배를 띄우기 전에#

떠 있는 물체를 CFD로 푸는 순간 이 값들은 전부 검증 기준이 된다.

첫째, 초기 흘수를 맞추기 전에 GMGM을 손으로 계산해 두자. VOF로 자유표면을 잡고(계면 포착 기법 비교) 6-DOF 강체 운동을 붙이면(쿼터니언 기반 강체 FEM) 배는 알아서 흔들린다. 그 자유 감쇠 진동의 주기 Tϕ=2πk/gGMT_\phi = 2\pi k/\sqrt{g\,GM}이 손으로 구한 값과 맞지 않으면 격자나 관성모멘트 입력을 의심해야 한다. kk는 횡동요 회전반경이다.

둘째, 격자 해상도가 BMBM을 직접 갉아먹는다. BMBM은 수선면의 x2x^2 가중 적분이라 현측 근처 셀이 가장 크게 기여한다. 자유표면이 두세 셀에 걸쳐 번지면 유효 수선면 폭이 흐려지고 복원력이 과소평가된다. 흘수선 부근은 국소 가늘게 잡는 편이 싸다.

셋째, 탱크를 채운 채 해석한다면 자유표면 손실이 해석 안에서 자동으로 나온다. 슬로싱과 함께 나온다는 뜻이다. 액체가 선체 운동과 위상이 어긋나면 준정적 δGM\delta GM보다 더 나쁠 수도, 나을 수도 있다. 자유수면을 음함수로 다루는 접근(Casulli–Zanolli의 중첩 Newton)이 필요한 것도 이 지점이다. 손 계산은 그때도 유효하다. 다만 정답이 아니라 결과를 의심할 기준선으로 쓰는 것이다.

도움이 됐다면 공유해주세요.