무게중심이 위에 있는데도 안 뒤집힌다 — 메타센터 GM과 자유표면 효과
GM은 수선면 관성모멘트가 벌어 주는 여유다. 탱크에 담긴 물의 자유표면 하나가 그 여유를 통째로 가져갈 수 있다.
컨테이너선의 무게중심은 물에 잠긴 부분의 중심보다 몇 미터 위에 있다. 무거운 쪽이 위에 얹혀 있는데도 배는 스스로 일어선다. 반대로 무게중심이 부력중심보다 아래인데 뒤집히는 부선(barge)도 있다. 위아래 관계만으로는 아무것도 결정되지 않는다는 뜻이다. 이 글은 그 판정을 실제로 내리는 양 — 메타센터 높이 — 이 어디서 나오는지, 그리고 탱크에 담긴 물이 왜 그 값을 통째로 지워버리는지를 다룬다.
무게중심이 부력중심보다 위에 있다#
기호부터 맞추자. 는 용골(keel, 배 바닥 중심), 는 무게중심, 는 부력중심이다. 부력중심은 물에 잠긴 부피의 도심(centroid)이다. 아르키메데스가 말한 것은 크기뿐이다. 부력의 크기는 밀어낸 물의 무게와 같고, 작용선은 잠긴 부피의 도심을 지난다.
똑바로 떠 있을 때 와 는 같은 연직선 위에 있다. 무게와 부력은 크기가 같고 방향이 반대라 모멘트가 0이다. 이 상태에서는 가 위든 아래든 아무 일도 일어나지 않는다. 안정성은 정지 상태가 아니라 살짝 기울였을 때 무엇이 생기는가로 정의된다.
기울이면 사정이 달라진다. 는 잠긴 모양의 도심이므로 잠긴 모양이 바뀌면 함께 옮겨 간다. 는 배와 함께 회전할 뿐 배 안에서는 제자리다. 두 연직선이 어긋나면서 모멘트 팔이 생긴다. 이것을 복원정(righting arm) 라 부르고, 복원 모멘트는 배수량 에 대해 다.
기울면 잠긴 모양이 바뀐다#
아래 시뮬레이션에서 직접 기울여보자. 폭·흘수·무게중심을 바꾸고 release at 15 deg를 눌러 돌아오는지 보면 된다.
(초록)가 기울인 쪽으로 끌려가고, (노랑)를 지나는 연직선과 벌어지는 간격이 다. 보라색 은 부력 작용선이 배 중심선과 만나는 점이다. KG 슬라이더를 7.33 m 위로 올리면 이 아래로 내려가고, 배는 돌아오는 대신 한쪽으로 누워 멈춘다.
— 수선면의 관성모멘트가 전부다#
작은 각도 에서 잠긴 모양의 변화는 쐐기 두 개로 정리된다. 한쪽 현측에서 물 밖으로 나온 쐐기와 반대쪽에서 새로 잠긴 쐐기다. 두 쐐기의 부피는 같고(배수량이 그대로여야 하므로) 좌우로 떨어져 있다. 이 부피 이동이 를 옮긴다.
수선면(waterplane, 수면이 배를 자른 단면)의 미소 띠 가 중심선에서 만큼 떨어져 있으면, 그 띠가 만드는 부피 변화는 이고 모멘트는 다. 전부 더하면 관성모멘트가 나온다.
는 중심선에 대한 수선면의 2차 관성모멘트, 는 배수 부피다. 폭 인 직사각형 단면이라면 단위 길이당 , 이므로
가 된다. 여기서 폭이 세제곱으로 들어갔다가 분모의 에서 한 번 상쇄되어 제곱으로 남는다는 점이 중요하다. 폭을 20% 넓히면 은 44% 커진다. 카누가 불안정하고 뗏목이 안정한 이유가 여기 있다. 그리고 판정식은 한 줄로 끝난다.
는 부력중심 높이, 는 무게중심 높이다. 이면 복원, 이면 전복이다. 폭 m, 흘수 m인 부선이면 m, m이므로 m다. 가 6.6 m라면 여유는 0.733 m뿐이다.
가 배신하는 각도#
은 의 기울기다. 작은 각도에서는 가 잘 맞는다. 문제는 이 근사가 언제 깨지는지다.
깨지는 지점은 기하학이 정한다. 수선면이 변하지 않는 동안만 가 상수이기 때문이다. 위 부선은 건현(freeboard, 수면 위 선체 높이)이 4 m이므로 , 즉 26.6°에서 갑판 끝이 물에 잠긴다. 그 순간부터 수선면은 더 넓어지지 못하고 오히려 줄어든다. 이 무너지고 곡선은 꺾인다.
위 시뮬레이션에서 hold heel을 누르고 각도를 30° 너머로 밀어보면 초록 실선(정확값)이 보라색 점선()에서 떨어져 나가는 것이 보인다. 두 곡선의 부호가 갈리는 각도가 곧 소멸각이다.
탱크 속 물이 GM을 통째로 가져간다#
이제 배 안에 액체가 담긴 탱크를 넣자. 밸러스트 탱크, 연료 탱크, 화물 탱크, 소화용수로 잠긴 자동차 갑판까지 전부 같은 문제다.
액체의 무게는 기울여도 변하지 않는다. 옮겨 갈 뿐이다. 그런데 그 이동이 를 아래쪽 현측으로 끌고 간다. 계산 구조는 과 똑같다. 탱크 안 자유표면도 하나의 수선면이고, 기울면 쐐기 두 개가 자리를 바꾼다.
는 탱크 자유표면의 관성모멘트, 는 탱크 액체 밀도, 는 바깥 물의 밀도다. 이 값은 배에서 실제로 빼는 것이 아니라 가 그만큼 위로 올라간 것처럼 취급한다(가상 상승, virtual rise). 탱크에 물이 얼마나 들어 있는지는 식에 없다. 넘칠 듯 가득 차 자유표면이 없어지거나 완전히 비지 않는 한, 20 cm가 담겼든 2 m가 담겼든 손실은 같다.
아래에서 칸막이 슬라이더를 움직여보자.
노란색이 탱크 액체다. 액체 표면은 선체가 어떻게 기울든 수평을 유지한다. n = 1에서 놓으면 배는 돌아오지 않고 한쪽으로 누워 버틴다(각도 부동, angle of loll). 칸막이를 하나 세우는 순간 빨간 손실 막대가 1/4로 줄고 배가 일어선다.
칸막이 하나가 손실을 1/4로 줄인다#
폭 인 탱크를 개로 나누면 각 칸의 폭은 이고 칸이 개다.
손실은 이 아니라 로 줄어든다. 폭의 세제곱이 개수의 1제곱을 이기기 때문이다. 위 부선에 폭 12 m 탱크가 하나 있으면 m⁴, m³/m이므로 m다. 0.733 m는 이미 한참 전에 사라졌다. 중앙에 칸막이 하나만 세우면 0.549 m로 떨어지고 배는 다시 선다.
이것이 유조선 중앙에 종격벽이 서 있는 이유다. 화물량도, 무게중심 높이도 바뀌지 않는다. 자유표면의 폭만 반이 된다.
Python으로 그려본 GZ 곡선과 소멸각#
은 접선의 기울기 하나일 뿐이다. 큰 각도의 는 잠긴 다각형을 직접 잘라서 도심을 구하는 편이 빠르다. 선체 단면을 다각형으로 놓고, 잠긴 면적이 배수 면적과 같아지는 수선 높이를 이분법으로 찾은 뒤, 그 다각형의 도심을 계산한다.
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 deg5°에서 오차는 2.7%다. 30°에서는 가 실제 의 절반밖에 안 된다. 갑판이 잠기기 전까지 수선면이 계속 넓어져 실제 복원력이 선형 예측보다 크기 때문이다. 그러다 45°를 넘으면 부호가 뒤집혀 이번에는 선형 근사가 안전 쪽으로 거짓말을 한다. 50°에서 실제 는 0인데 는 여전히 0.56 m를 약속한다.
자유표면 손실은 다각형을 자르지 않고도 확인할 수 있다. 갑판이 잠기기 전까지는 현측이 수직이라 복원정에 닫힌 식이 있기 때문이다. 손실은 가상 상승이므로 에서 을 빼고 넣으면 된다.
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칸막이 하나로 가 144에서 36으로, 정확히 1/4이 된다. 20°에서의 복원정은 −0.379 m에서 +0.184 m로 부호가 바뀐다. 여기 쓴 닫힌 식 는 갑판이 잠기기 전 구간에서 다각형 계산과 일치한다. 손실이 없을 때 20°의 값 0.3716 m가 위 표와 같다.
6-DOF VOF 해석에 배를 띄우기 전에#
떠 있는 물체를 CFD로 푸는 순간 이 값들은 전부 검증 기준이 된다.
첫째, 초기 흘수를 맞추기 전에 을 손으로 계산해 두자. VOF로 자유표면을 잡고(계면 포착 기법 비교) 6-DOF 강체 운동을 붙이면(쿼터니언 기반 강체 FEM) 배는 알아서 흔들린다. 그 자유 감쇠 진동의 주기 이 손으로 구한 값과 맞지 않으면 격자나 관성모멘트 입력을 의심해야 한다. 는 횡동요 회전반경이다.
둘째, 격자 해상도가 을 직접 갉아먹는다. 은 수선면의 가중 적분이라 현측 근처 셀이 가장 크게 기여한다. 자유표면이 두세 셀에 걸쳐 번지면 유효 수선면 폭이 흐려지고 복원력이 과소평가된다. 흘수선 부근은 국소 가늘게 잡는 편이 싸다.
셋째, 탱크를 채운 채 해석한다면 자유표면 손실이 해석 안에서 자동으로 나온다. 슬로싱과 함께 나온다는 뜻이다. 액체가 선체 운동과 위상이 어긋나면 준정적 보다 더 나쁠 수도, 나을 수도 있다. 자유수면을 음함수로 다루는 접근(Casulli–Zanolli의 중첩 Newton)이 필요한 것도 이 지점이다. 손 계산은 그때도 유효하다. 다만 정답이 아니라 결과를 의심할 기준선으로 쓰는 것이다.
관련
도움이 됐다면 공유해주세요.