下面給你一個「能即時查詢、幾乎零延遲」的做法集合:你可以用近似 CFD 的方式把風場先算成一個可查詢的 3D 向量場(vector field),之後「取任意位置與高度」就只是 O(1) 的插值,不會卡。

我會給兩條路線:(A) 2.5D 超快方案(最推薦,遊戲/互動最穩)與 (B) 真 3D 穩定流體方案(更真但成本更高,建議上 GPU)。


你要的問題抽象成什麼

你有:

  • 地形高度圖(heightmap):100×200(可視為 H[x,y])

  • 風從左吹到右:左邊界(x=0)整條 100 格都有固定入流 u = (U0, 0, 0)

  • 邊界風速固定:外框(所有邊界)給定 Dirichlet 邊界(Dirichlet boundary condition)

  • 風是三維:你要任意 (x,y,z) 輸出 v(x,y,z) = (u,v,w)(wind velocity vector)

你真正需要的是:

  1. 先生成/預計算一個可用的 3D 風場 V[x,y,z]

  2. 查詢時做 trilinear interpolation(三線性插值) 即可,幾乎無成本


(A) 2.5D「超快、零延遲」風場:2D 壓力投影 + 地形抬升(orographic lift)

這條路線在遊戲裡超常用:把主要計算放在 2D 平面(x,y)做**不可壓縮(incompressible)**的水平風,再用地形坡度把垂直分量 w 補出來,形成「3D 感」。

A1. 先做 2D 的水平風:解一個 Poisson(Poisson equation)得到無散度風場

令水平速度 U(x,y) = (u,v)。

  1. 初始化
  • 全域初始:U = (U0, 0)(或逐步從左到右衰減也可)

  • 邊界固定:外框 U = U_boundary(你說邊緣全固定就直接套)

  1. 加入地形阻力(terrain drag / permeability)

    地形越陡、越高,越容易造成近地層減速、繞流:

  • 你可以做一張「阻力圖」D[x,y],例如

    • slope = |∇H|(gradient / slope)

    • D = d0 + d1 * slope + d2 * normalize(H)

  • 然後每輪把速度做衰減:U ← U * exp(-Ddt)(或 U -= DU*dt)

  1. 壓力投影(pressure projection)讓風「守恆」

    用 Stable Fluids(Stable Navier–Stokes, Jos Stam)常見的做法:

  • 先算散度:div = ∂u/∂x + ∂v/∂y

  • 解 Poisson:∇²p = div

  • 用壓力梯度修正:U ← U - ∇p

這一步保證水平場近似不可壓縮,流線會自然繞山、穿谷。

重點:Poisson 可以用少量迭代就夠

因為你要的是「看起來合理 + 可查詢」,不是科研精度。

常用:Jacobi / Gauss-Seidel(迭代 20~60 次)就很平滑了。100×200 的 2D Poisson 超快。


A2. 把 2D 變成 3D:用地形造成的垂直速度 

w

你要的 w 不必真的解 3D Navier–Stokes,也能非常像:

方法 1:坡向抬升(orographic lift)

  • 風撞上迎風坡會上升:

    w_surface(x,y) = k * dot(U, ∇H)

    • ∇H 是坡度方向(越陡越大)

    • dot(U, ∇H) 表示風是否往上坡吹(迎風為正,背風為負)

方法 2:用「水平收縮/發散」推 w(mass continuity 近似)

  • 你已經做了水平近似無散度,但加了阻力/障礙後局部仍會有「有效散度」,可做:

    w_surface = -α * div_effective

    直覺:水平被迫收縮 → 只能向上走。


A3. 做高度方向的剖面(vertical profile)讓它真的像 3D

你提到「縱深 140–170」,我用最常見的方式處理:把 z 當高度(height / altitude),只關心某個高度帶或幾層。

做法:把 (u,v,w) 從地表往上做衰減/增強:

  • 近地層風速隨高度增強,常用 power law(power-law profile):

    • speed(z) = speed_ref * (z / z_ref)
  • 垂直分量 w 通常在高處快速衰減:

    • w(z) = w_surface * exp(-z / z_w)

最後得到:

  • u(x,y,z) = u2D(x,y) * f(z)

  • v(x,y,z) = v2D(x,y) * f(z)

  • w(x,y,z) = w_surface(x,y) * g(z)


A4. 「無延遲查詢」的關鍵:預烘焙成 3D 格點場 + 插值

你要做到查詢瞬間回 (u,v,w),就不要現算 PDE。

建議:

  • 選一個 z 層數 Nz:例如 16 或 24 層(夠用了)

  • 預先算出 V[x,y,zLayer] 存起來

  • 查詢 (x,y,z):

    1. 找到四周格點

    2. 做 trilinear interpolation(三線性插值)

    3. 回傳 Vector3

記憶體估算(很小)

100×200×16 = 320k cells

每格存 3 個 float(Float32)= 12 bytes

總共 ~3.8 MB,超輕。


(B) 真 3D「更像 CFD」:Stable Fluids / LBM(建議 GPU)

如果你想要更真:山後渦流、回捲、垂直剪切更自然,那就做 3D solver。

B1. 3D Stable Fluids(Navier–Stokes 近似)

每個時間步:

  1. Add force(加入外力,如主風、浮力)

  2. Advect(semi-Lagrangian advection,半拉格朗日平流)

  3. Diffuse(黏性擴散,可選)

  4. Project(3D Poisson 壓力投影,強制無散度)

地形作為固體(solid cells):

  • 若 z < H[x,y] 則該 cell 為 solid,速度強制 0 或做 no-slip(no-slip boundary)

但 CPU 上 100×200×(例如 32) 會比較吃力,尤其 Poisson 3D。

若你用 WebGPU / Compute Shader(compute shader)或 Unity Compute Shader 就非常爽。

B2. Lattice Boltzmann Method(LBM)

LBM(例如 D3Q19)在格點流體上很穩、平行化很好做,GPU 極強。

缺點:實作複雜一點,但效果漂亮。


我會怎麼選(我的建議)

  • 你明確要求「無延遲、不能卡」而且格子只有 100×200:

    選 (A) 2.5D:2D Poisson 幾十次迭代就能得到很平滑的繞流效果,再用坡度補 w + 高度剖面。

    查詢是 O(1) 插值,幾乎 0 成本。

  • 只有在你要「風洞級」渦街(vortex shedding)那種視覺,才上 (B) 真 3D,並且最好 GPU。


具體落地:你需要的資料結構與流程(可直接照做)

Step 0:網格與座標

  • 地圖索引:x∈[0,99], y∈[0,199]

  • 世界座標:X = x * cellSize, Y = y * cellSize

  • 高度:H[x,y](你給的那些數就是每格高度)

Step 1:預處理

  • 算梯度(gradient)∂H/∂x, ∂H/∂y(用 central difference)

  • 算坡度 slope = sqrt(gx^2 + gy

  • 建阻力圖 D[x,y] = d0 + d1slope + d2normalize(H)

Step 2:初始化水平風 

U(x,y)

  • U = (U0,0) 全圖

  • 邊界固定(Dirichlet):

    • 左邊界:U=(U0,0)

    • 其他邊界:你說固定,就給你設定值(可同 U0 或稍微弱)

Step 3:迭代求穩態(steady state)

跑 N 次(例如 60~200 次,取決於你想多平滑;這是預計算,甚至可以在載入畫面做)

每次:

  1. 阻力衰減:U ← U - dt * D * U

  2. 壓力投影:

    • div = du/dx + dv/dy

    • 解 ∇²p = div(Jacobi/Gauss-Seidel 迭代 30~80 次)

    • U ← U - ∇p

Step 4:生成垂直分量 

w_surface(x,y)

  • w_surface = k * dot(U, ∇H)(迎風上升、背風下沉)

  • 可加 clamp 限制:w_surface = clamp(w_surface, -wMax, wMax)

Step 5:擴展到 Nz 層,得到 3D 場 

V[x,y,zLayer]

對每個 zLayer(例如 z=0..15):

  • 設 z = zMin + (zMax-zMin)*layer/(Nz-1)

    (你提到 140–170,就可以讓 zMin=140, zMax=170)

  • f(z)(水平剖面):power law

  • g(z)(垂直剖面):exp decay

  • V = (u2Df, v2Df, w_surface*g)

Step 6:查詢(即時)

輸入 (x,y,z):

  • 找到最近的 (ix,iy,iz) 與比例 (tx,ty,tz)

  • 在 V 做 trilinear interpolation

  • 回傳 Vector3(u,v,w),風速(wind speed)就是 |V|


你會立刻得到的效果(直覺)

  • 風遇到「山脊」:水平變慢並繞流,迎風坡 w>0 上升

  • 山谷:水平會被導流(channeling effect),速度增強

  • 背風側:w<0 下沉,且可以加一點渦旋噪聲(curl noise)做「亂流感」


進一步讓它更「真」但仍然快(加分技巧)

  1. 加一個湍流貼圖(turbulence / curl noise)

    在背風側(lee side)或坡度大處,加小幅度旋度噪聲,視覺會很像真 CFD,但成本幾乎 0。

  2. 把阻力做成「高度相關」

    近地層阻力大、高空阻力小:D(z) = D0 * exp(-z/z0)

    這樣高度剖面會更自然。

  3. 分區域預算

    你只要查詢玩家附近,就只烘焙局部 3D tiles(tile cache),做到更低記憶體、動態地圖也能跑。


你現在可以直接做的最小可行版本(MVP)

如果你今天就要跑起來,而且要快到「完全不會卡」:

  • Nz=16

  • 2D Poisson:每次投影用 Jacobi 40 iter

  • 外層穩態迭代:80 iter

  • w_surface = k * dot(U, ∇H)

  • 查詢用三線性插值

這套基本上就能在載入時幾十毫秒~數百毫秒內完成(看你用 JS/TS/Unity/Java),之後每次查詢是「幾個 array 讀取 + 插值」,完全無延遲。


如果你告訴我你是要在 Web(TS + Three.js/WebGL/WebGPU)Unity、還是 Java 裡跑,我可以把上面這套直接變成你可用的具體代碼結構(包含:資料陣列 layout、Poisson solver 迭代寫法、邊界處理、以及 getWind(x,y,z) 的查詢函式),並按照你要求的「整段可直接貼進去用」的風格給你。