Master Engineering & Neuroscience

유체역학 수상 아키텍처: 게르스트너 파도 합산, EditableMesh 정점 변형 및 선체 다점 부력 시뮬레이션

By DopaBrain 유체역학 및 엔진 물리 연구팀 • 2026-10-01
2048 코치 게르스트너 파동 삼각함수 합산, EditableMesh 정점 변위 및 유체 미적분 연산 반응속도 테스트 파도 마루 도달 타이밍, 해양 유체역학 및 선박 스로틀 조향 반사 잠복기 뇌 유형 테스트 공간 유체 시뮬레이션 및 다중 프로브 아르키메데스 인지 아키타입 스트레스 지수 폭풍우 너울 어지럼증, 롤링/피칭 전복 공포 및 파도 안정 평정심

로블록스의 현대 오픈월드, 해전, 항해 탐험 게임에서 기본 지형(Smooth Terrain) 워터는 사실적인 해양 게임플레이에 필요한 동적 파고, 커스텀 조류, 정밀한 부력 물리를 온전히 제공하지 못합니다. 폭풍우의 거친 파도나 사실적인 보트 롤링/피칭을 원하는 개발자에게는 커스텀 유체역학 파이프라인이 필수적입니다.

Luau에서 사실적인 바다를 구현하려면 수학적 엄밀성과 60fps 실시간 성능 간의 균형을 잡아야 합니다. 본 마스터 가이드에서는 고성능 게르스트너(Gerstner) 파도 엔진을 구축합니다. 파동 해석 수식을 분석하고, 표면 법선 벡터를 도출하며, EditableMesh로 정점을 변형하고, 다중 프로브 아르키메데스 부력과 메타센터 복원 토크를 완성합니다.

1. 물리 중심 게임플레이에서 기본 지형 워터의 한계

로블록스 내장 지형 워터는 단순 수영과 부유는 지원하지만, 본격적인 항해 시뮬레이션에서는 심각한 한계에 직면합니다:

2. 수학적 기초: 게르스트너 파도 합산 및 법선 벡터 도출

단순 사인파는 둥근 마루와 골을 만들지만, 실제 해양 파도는 날카로운 트로코이드형 마루와 넓은 골을 보입니다. 게르스트너 파도는 정점을 마루 방향으로 수평 이동시켜 이를 완벽히 재현합니다:

3. 완전한 게르스트너 파도 & 선체 부력 솔버 Luau 구현체

임의의 월드 좌표에서 파고와 법선을 산출하고 선체에 분포된 프로브 부력과 복원 토크를 연산하는 프로덕션 Luau 모듈입니다:

GerstnerBuoyancySolver.luau (해석적 파동 및 분산 유체역학)
--!strict
local RunService = game:GetService("RunService")

export type WaveDef = {
    Direction: Vector2,
    Amplitude: number,
    Wavelength: number,
    Speed: number,
    Steepness: number,
}

export type WaterHull = {
    Model: Model,
    RootPart: BasePart,
    ProbeOffsets: { Vector3 },
    SubmergedVolumePerProbe: number,
    WaterDensity: number,
    LinearDamping: number,
    AngularDamping: number,
}

local GerstnerSolver = {}
GerstnerSolver.__index = GerstnerSolver

local GRAVITY = 196.2

function GerstnerSolver.new(waves: { WaveDef })
    local self = setmetatable({}, GerstnerSolver)
    self.Waves = waves
    return self
end

function GerstnerSolver:GetWaveHeightAndNormal(worldPos: Vector3, timeSec: number): (number, Vector3)
    local x = worldPos.X
    local z = worldPos.Z
    local totalY = 0
    local dDx = 0
    local dDz = 0

    for _, wave in ipairs(self.Waves) do
        local dir = wave.Direction.Unit
        local k = (2 * math.pi) / wave.Wavelength
        local omega = math.sqrt(GRAVITY * k)
        local phase = k * (dir.X * x + dir.Y * z) - omega * timeSec

        local cosP = math.cos(phase)
        local sinP = math.sin(phase)

        totalY += wave.Amplitude * cosP
        dDx += -dir.X * (k * wave.Amplitude) * sinP
        dDz += -dir.Y * (k * wave.Amplitude) * sinP
    end

    local normal = Vector3.new(-dDx, 1, -dDz).Unit
    return totalY, normal
end

function GerstnerSolver:ApplyBuoyancy(hull: WaterHull, dt: number, timeSec: number)
    local root = hull.RootPart
    local rootCF = root.CFrame
    local rootVel = root.AssemblyLinearVelocity
    local rootAngVel = root.AssemblyAngularVelocity
    local com = root.AssemblyCenterOfMass

    local totalForce = Vector3.zero
    local totalTorque = Vector3.zero

    for _, offset in ipairs(hull.ProbeOffsets) do
        local probeWorld = rootCF:PointToWorldSpace(offset)
        local waveHeight, waveNormal = self:GetWaveHeightAndNormal(probeWorld, timeSec)
        local depth = waveHeight - probeWorld.Y

        if depth > 0 then
            local displacedVolume = math.min(depth * 1.5, 1.0) * hull.SubmergedVolumePerProbe
            local buoyantMagnitude = displacedVolume * hull.WaterDensity * GRAVITY
            local buoyantForce = Vector3.new(0, buoyantMagnitude, 0)

            local probeVel = rootVel + rootAngVel:Cross(probeWorld - com)
            local dragForce = -probeVel * (hull.LinearDamping * displacedVolume)

            local netProbeForce = buoyantForce + dragForce
            totalForce += netProbeForce

            local leverArm = probeWorld - com
            totalTorque += leverArm:Cross(netProbeForce)
        end
    end

    local angDampingTorque = -rootAngVel * hull.AngularDamping
    totalTorque += angDampingTorque

    root:ApplyAssemblyForce(totalForce)
    root:ApplyAssemblyTorque(totalTorque)
end

return GerstnerSolver

4. EditableMesh 실시간 파도 정점 변형 기법

물리 부력 시뮬레이션과 100% 일치하는 동적 게르스트너 파도 그래픽 렌더링 파이프라인:

5. 프로덕션 최적화 및 멀티플레이어 동기화 아키텍처

멀티플레이어 환경에서 대규모 동적 수면을 최적화하기 위한 엔지니어링 규칙:

Frequently Asked Questions

단순 사인파 대신 게르스트너 파도를 사용하는 이유는 무엇인가요?

일반 사인파는 마루와 골이 대칭적인 곡선을 이룹니다. 하지만 실제 바다 파도는 마루가 뾰족하고 골이 넓습니다. 게르스트너 파도는 정점을 마루 쪽으로 수평 이동시켜 실제 트로코이드 해양 파도와 보트의 롤링/피칭을 사실적으로 재현합니다.

EditableMesh는 수천 개의 파트를 움직이는 것과 비교해 얼마나 빠른가요?

수천 개의 BasePart를 움직이면 씬 그래프 갱신, 물리 감지, 드로우 콜 병목이 발생합니다. 반면 EditableMesh는 메모리의 단일 정점 버퍼를 직접 수정하므로 수천 개의 정점 변위를 몇 밀리초 내에 처리합니다.

보트가 전복되거나 뒤집히지 않게 하려면 어떻게 해야 하나요?

부심(CB)과 무게중심(COM) 사이의 메타센터 높이(GM)를 양수로 유지해야 합니다. 선체 하부의 부력 프로브를 무게중심보다 좌우로 넓게 배치하면 배가 기울어질 때 자동으로 일어서는 복원 모멘트가 발생합니다.

멀티플레이어에서 모든 플레이어에게 동일한 파도가 보이나요?

네. 게르스트너 파도는 파동 공식과 시간에 기반한 결정론적 수식이므로, 서버 동기화 시간(Workspace:GetServerTimeNow())을 공유하면 모든 클라이언트가 완벽히 동일한 파고와 수면을 계산합니다.

Explore More Interactive Tests & Guides

Discover personalized cognitive assessments, stress evaluations, and game psychology tools on DopaBrain.

Go to Portal Hub