下面給你一個「能即時查詢、幾乎零延遲」的做法集合:你可以用近似 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)
你真正需要的是:
-
先生成/預計算一個可用的 3D 風場 V[x,y,z]
-
查詢時做 trilinear interpolation(三線性插值) 即可,幾乎無成本
(A) 2.5D「超快、零延遲」風場:2D 壓力投影 + 地形抬升(orographic lift)
這條路線在遊戲裡超常用:把主要計算放在 2D 平面(x,y)做**不可壓縮(incompressible)**的水平風,再用地形坡度把垂直分量 w 補出來,形成「3D 感」。
A1. 先做 2D 的水平風:解一個 Poisson(Poisson equation)得到無散度風場
令水平速度 U(x,y) = (u,v)。
- 初始化
-
全域初始:U = (U0, 0)(或逐步從左到右衰減也可)
-
邊界固定:外框 U = U_boundary(你說邊緣全固定就直接套)
-
加入地形阻力(terrain drag / permeability)
地形越陡、越高,越容易造成近地層減速、繞流:
-
你可以做一張「阻力圖」D[x,y],例如
-
slope = |∇H|(gradient / slope)
-
D = d0 + d1 * slope + d2 * normalize(H)
-
-
然後每輪把速度做衰減:U ← U * exp(-Ddt)(或 U -= DU*dt)
-
壓力投影(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):
-
找到四周格點
-
做 trilinear interpolation(三線性插值)
-
回傳 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 近似)
每個時間步:
-
Add force(加入外力,如主風、浮力)
-
Advect(semi-Lagrangian advection,半拉格朗日平流)
-
Diffuse(黏性擴散,可選)
-
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 次,取決於你想多平滑;這是預計算,甚至可以在載入畫面做)
每次:
-
阻力衰減:U ← U - dt * D * U
-
壓力投影:
-
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)做「亂流感」
進一步讓它更「真」但仍然快(加分技巧)
-
加一個湍流貼圖(turbulence / curl noise)
在背風側(lee side)或坡度大處,加小幅度旋度噪聲,視覺會很像真 CFD,但成本幾乎 0。
-
把阻力做成「高度相關」
近地層阻力大、高空阻力小:D(z) = D0 * exp(-z/z0)
這樣高度剖面會更自然。
-
分區域預算
你只要查詢玩家附近,就只烘焙局部 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) 的查詢函式),並按照你要求的「整段可直接貼進去用」的風格給你。