โ† ๋ฐฑ์„œ ๋ฐ ๊ธฐ์ˆ  ๋ธ”๋กœ๊ทธ ๋ชฉ๋ก์œผ๋กœ ๋Œ์•„๊ฐ€๊ธฐ
ZeliDesk Core Engineering Architecture Milestone Whitepaper #760 ๐Ÿ†

[์ œ760ํ˜ธ ๊ธฐ๋… ๐Ÿ†] ์ดˆยท์ค‘ยท๊ณ  ์ •๋ณด ๊ณผํ•™ยท์ปดํ“จํ„ฐ ๊ทธ๋ž˜ํ”ฝ์Šค ๊ต์œก๊ณผ ๊ตญ๋ฐฉ ํ•ญ๊ณต ์‹œ๋ฎฌ๋ ˆ์ดํ„ฐ ์‹ค์‹œ๊ฐ„ ๋Œ€๊ทœ๋ชจ ์ง€ํ˜• ๋ Œ๋”๋ง(CDLOD): ZeliDesk์˜ ์˜จ๋””๋ฐ”์ด์Šค ์ˆœ์ˆ˜ C# ์‚ฌ๋ถ„ํŠธ๋ฆฌ(Quadtree) ์ ˆ๋‘์ฒด ์ปฌ๋ง, ์—ฐ์† ๊ฑฐ๋ฆฌ ๊ธฐ๋ฐ˜ LOD ๋ชจํ•‘, 0-Alloc SIMD ๊ณ ๋„ ๊ฒฉ์ž ๋ฉ”์‹œ, 120fps ํ•œ๋ฐ˜๋„ ์ „์žฅ ๋””์ง€ํ„ธ ํŠธ์œˆ ์ง€ํ˜• ์—”์ง„ ์•„ํ‚คํ…์ฒ˜

1. ๊ฐœ์š”: 2022 ๊ฐœ์ • ๊ณต๊ฐ„ ๊ธฐํ•˜ยทํŠธ๋ฆฌ ๊ตฌ์กฐ ๊ต์œก๊ณผ ๊ตญ๋ฐฉ ๊ฐ€์ƒ ํ›ˆ๋ จ ์‹œ๋ฎฌ๋ ˆ์ดํ„ฐ ์ฃผ๊ถŒ

2022 ๊ฐœ์ • ๊ต์œก๊ณผ์ • ์ •๋ณด(์ž๋ฃŒ๊ตฌ์กฐ, ์•Œ๊ณ ๋ฆฌ์ฆ˜, ์ปดํ“จํ„ฐ ๊ทธ๋ž˜ํ”ฝ์Šค) ๋ฐ ๊ธฐํ•˜(๊ณต๊ฐ„ ์ขŒํ‘œ, ํ‰๋ฉด์˜ ๋ฐฉ์ •์‹, ์‹œ์•ผ ์ ˆ๋‘์ฒด Frustum)์—์„œ ๋Œ€๊ทœ๋ชจ 3์ฐจ์› ๊ณต๊ฐ„ ๋ถ„ํ• (Spatial Partitioning)๊ณผ ์ ์‘ํ˜• LOD(Level of Detail)๋Š” ์ปดํ“จํ„ฐ๊ฐ€ ํ•œ์ •๋œ ๋ฉ”๋ชจ๋ฆฌ์™€ ์—ฐ์‚ฐ ์ž์›์œผ๋กœ ๋ฌดํ•œ์— ๊ฐ€๊นŒ์šด ๋ฐฉ๋Œ€ํ•œ 3์ฐจ์› ๊ฐ€์ƒ ์„ธ๊ณ„๋ฅผ ์‹ค์‹œ๊ฐ„์œผ๋กœ ํ‘œํ˜„ํ•˜๋Š” ์ „์‚ฐํ•™์˜ ์ •์ˆ˜์ž…๋‹ˆ๋‹ค. ํ•™์ƒ๋“ค์€ ํ‰๋ฉด์„ 4๊ฐœ์˜ ์‚ฌ๋ถ„๋ฉด์œผ๋กœ ์žฌ๊ท€ ๋ถ„ํ• ํ•˜๋Š” ์‚ฌ๋ถ„ํŠธ๋ฆฌ(Quadtree) ์ž๋ฃŒ๊ตฌ์กฐ์™€, ์นด๋ฉ”๋ผ๋กœ๋ถ€ํ„ฐ์˜ ๊ฑฐ๋ฆฌ์— ๋”ฐ๋ผ ๋ฉ”์‹œ ํ•ด์ƒ๋„๋ฅผ ๋™์ ์œผ๋กœ ์กฐ์ ˆํ•˜๋Š” ์ˆ˜ํ•™์  ๋ณด๊ฐ„๋ฒ•์„ ํ•™์Šตํ•˜๋ฉฐ ๊ณต๊ฐ„ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ์˜ ๋ณธ์งˆ์„ ๊นจ๋‹ซ์Šต๋‹ˆ๋‹ค.

๋™์‹œ์— ๋Œ€ํ•œ๋ฏผ๊ตญ ๊ณต๊ตฐยท์œก๊ตฐ ํ•ญ๊ณต์ž‘์ „์‚ฌ๋ น๋ถ€์˜ KF-21/์ˆ˜๋ฆฌ์˜จ ๋น„ํ–‰ ์‹œ๋ฎฌ๋ ˆ์ดํ„ฐ ๋ฐ ๋ฌด์ธ๊ธฐ ์ง€์ƒํ†ต์ œ์†Œ(GCS: Ground Control Station)์—์„œ ํ•œ๋ฐ˜๋„ ์ „์—ญ(์ˆ˜๋ฐฑ km × ์ˆ˜๋ฐฑ km)์˜ ์ •๋ฐ€ ๊ณ ๋„ ์ง€ํ˜•(DTED Level 2 / ์ˆ˜์น˜ํ‘œ๊ณ ๋ชจ๋ธ DEM)์„ 120fps๋กœ ์ง€ํ„ฐ ์—†์ด ๋ Œ๋”๋งํ•˜๋Š” ๊ฒƒ์€ ์กฐ์ข…์‚ฌ์˜ ์ƒ์กด์„ฑ๊ณผ ์‹ค์ „ ์ž„๋ฌด ์™„์ˆ˜๋ฅผ ๋ณด์žฅํ•˜๋Š” ํ•ต์‹ฌ ๊ฐ€์ƒ ํ›ˆ๋ จ ๊ธฐ๋ฐ˜์ž…๋‹ˆ๋‹ค. ๋งŒ์•ฝ LOD ์ „ํ™˜ ์‹œ ์ง€ํ˜•์ด ํŠ€๋Š” ํ˜„์ƒ(Popping Artifact)์ด ๋ฐœ์ƒํ•˜๊ฑฐ๋‚˜, ํ•ด์ƒ๋„๊ฐ€ ๋‹ค๋ฅธ ํƒ€์ผ ๊ฒฝ๊ณ„์—์„œ ํ‹ˆ์ƒˆ(T-Junction Crack)๊ฐ€ ๋ฒŒ์–ด์ง€๋ฉด ์กฐ์ข…์‚ฌ์˜ ๊ณต๊ฐ„ ์ง€๊ฐ์— ์น˜๋ช…์ ์ธ ์™œ๊ณก์„ ์ดˆ๋ž˜ํ•ฉ๋‹ˆ๋‹ค.

๊ธฐ์กด ๊ตญ๋ฐฉ ์‹œ๋ฎฌ๋ ˆ์ดํ„ฐ๋Š” ์ˆ˜์–ต ์›์˜ ์™ธ์‚ฐ ์ƒ์šฉ ์—”์ง„(Unreal Engine / Presagis Vega Prime) ๋ผ์ด์„ ์Šค์— ์–ฝ๋งค์—ฌ ๊ตญ์‚ฐ ์ „์ˆ  C4I ์žฅ๋น„ ๋‚ด ๋…๋ฆฝ ํƒ‘์žฌ๊ฐ€ ๋ถˆ๊ฐ€๋Šฅํ–ˆ์Šต๋‹ˆ๋‹ค. [์ œ760ํ˜ธ ๊ธฐ๋…์ž‘]์œผ๋กœ ZeliDesk๋Š” ์™ธ์‚ฐ ์ƒ์šฉ ๋„๊ตฌ๋ฅผ 100% ํƒˆํ”ผํ•˜๊ณ , .NET 9 ์ˆœ์ˆ˜ C# ๊ธฐ๋ฐ˜์˜ ์˜จ๋””๋ฐ”์ด์Šค 3์ฐจ์› CDLOD(Continuous Distance-Dependent Level of Detail) ์‹ค์‹œ๊ฐ„ ์ง€ํ˜• ์—”์ง„์„ ์™„์„ฑํ•˜์˜€์Šต๋‹ˆ๋‹ค. ์žฌ๊ท€์  ์‚ฌ๋ถ„ํŠธ๋ฆฌ ํ‰๋ฉด ์ ˆ๋‘์ฒด ์ปฌ๋ง, ๋‹จ์ผ ๊ณ ๋„ ๊ฒฉ์ž ๋ฉ”์‹œ(Grid Mesh) ์ธ์Šคํ„ด์‹ฑ ์žฌ์‚ฌ์šฉ, ๋ฒ„ํ…์Šค ์…ฐ์ด๋” ๋‚ด ์—ฐ์† ๋ชจํ•‘ ์ธ์ž($\alpha$-Morphing) ๊ณ„์‚ฐ, ๋ฐ 0-Alloc ๊ตฌ์กฐ์ฒด ํ’€๋ง์„ ํ†ตํ•ด 120fps ๋ฌด์†์‹ค ํ•œ๋ฐ˜๋„ ์ „์žฅ ๊ฐ€์ƒํ™” ์ฒญ์‚ฌ์ง„์„ ์ „๊ฒฉ ๊ณต๊ฐœํ•ฉ๋‹ˆ๋‹ค.

2. ํ•™์ˆ ์  ๋ฐฐ๊ฒฝ: CDLOD ์‚ฌ๋ถ„ํŠธ๋ฆฌ ์„ ํƒ ๊ธฐ์ค€ ๋ฐ T-Junction ๋ฐฉ์ง€ $\alpha$-๋ชจํ•‘ ์ˆ˜ํ•™ ๋ชจ๋ธ

์ง€ํ˜• ๊ณต๊ฐ„์„ 2์ฐจ์› ์‚ฌ๋ถ„ํŠธ๋ฆฌ ๋…ธ๋“œ๋กœ ๋ถ„ํ• ํ•˜๋ฉฐ, ๋ฃจํŠธ ๋…ธ๋“œ(Level $L_{\max}$)๋ถ€ํ„ฐ ๋ฆฌํ”„ ๋…ธ๋“œ(Level 0)๊นŒ์ง€ ๊ณ„์ธต์ ์œผ๋กœ ๊ตฌ์„ฑ๋ฉ๋‹ˆ๋‹ค. ์นด๋ฉ”๋ผ ์œ„์น˜ $\mathbf{P}_{\text{cam}}$๊ณผ ์‚ฌ๋ถ„ํŠธ๋ฆฌ ๋…ธ๋“œ์˜ 3D ๋ฐ”์šด๋”ฉ ๋ฐ•์Šค(AABB) ๊ฐ„์˜ ์ตœ๋‹จ ๊ฑฐ๋ฆฌ $D_{\min}$์„ ํ‰๊ฐ€ํ•ฉ๋‹ˆ๋‹ค. ๊ฐ LOD ๋ ˆ๋ฒจ $l$์— ํ• ๋‹น๋œ ๋ Œ๋”๋ง ๊ฐ€์‹œ ๊ฑฐ๋ฆฌ ๋ฒ”์œ„๋ฅผ $R_l$์ด๋ผ ํ•  ๋•Œ, ์‚ฌ๋ถ„ํŠธ๋ฆฌ ๋…ธ๋“œ์˜ ๋ถ„ํ•  ์กฐ๊ฑด์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค:

$$\text{Split}(Node_l) \iff (l > 0) \land (D_{\min} \le R_l)$$

๋…ธ๋“œ๊ฐ€ ๋ถ„ํ• ๋˜์ง€ ์•Š๊ณ  ์„ ํƒ๋˜๋ฉด, ํ•ด๋‹น ๋…ธ๋“œ๋Š” LOD ๋ ˆ๋ฒจ $l$์˜ ๊ท ์ผ ํ‰๋ฉด ๊ฒฉ์ž ๋ฉ”์‹œ($k \times k$ ์ •์ )๋ฅผ ๊ณต์œ  ์ธ์Šคํ„ด์‹ฑํ•˜์—ฌ ๋ Œ๋”๋งํ•ฉ๋‹ˆ๋‹ค. ์ด๋•Œ ์ด์›ƒํ•œ ์ €ํ•ด์ƒ๋„ ํƒ€์ผ($l+1$)๊ณผ์˜ ๊ฒฝ๊ณ„์—์„œ ๋ฐœ์ƒํ•˜๋Š” ์ •์  ๋ถˆ์ผ์น˜(T-Junction) ๋ฐ ๊ฑฐ๋ฆฌ ๋ณ€ํ™”์— ๋”ฐ๋ฅธ ํŒํ•‘(Popping)์„ ์™„์ „ํžˆ ์ œ๊ฑฐํ•˜๊ธฐ ์œ„ํ•ด ์ •์  ๋ชจํ•‘ ์ธ์ž $\alpha \in [0, 1]$๋ฅผ ๊ฑฐ๋ฆฌ $d$์— ๋Œ€ํ•ด ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ •์˜ํ•ฉ๋‹ˆ๋‹ค:

$$\alpha = \text{clamp}\left( \frac{d - (R_l - W_l)}{W_l}, 0, 1 \right)$$

์—ฌ๊ธฐ์„œ $W_l$์€ ๋ชจํ•‘ ์ „์ด ๊ตฌ๊ฐ„(Transition Width)์ž…๋‹ˆ๋‹ค. ์ •์  ์œ„์น˜ $\mathbf{v}$์˜ ๊ฒฉ์ž ์ขŒํ‘œ $(i, j)$๊ฐ€ ํ™€์ˆ˜์ผ ๋•Œ, ๋ถ€๋ชจ ๋ ˆ๋ฒจ($l+1$)์˜ ์ง์ˆ˜ ์ด์›ƒ ์ •์  ํ‰๊ท ๊ฐ’ $\mathbf{v}_{\text{coarse}}$๋กœ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์„ ํ˜• ๋ณด๊ฐ„(Lerp)ํ•ฉ๋‹ˆ๋‹ค:

$$\mathbf{v}_{\text{morphed}} = (1 - \alpha)\mathbf{v} + \alpha \mathbf{v}_{\text{coarse}}$$

$\alpha = 0$์ด๋ฉด ์™„์ „ํ•œ ๊ณ ํ•ด์ƒ๋„ ํ˜•์ƒ์„ ์œ ์ง€ํ•˜๊ณ , ๊ฑฐ๋ฆฌ๊ฐ€ ๋ฉ€์–ด์ ธ $\alpha \to 1$๋กœ ์ ‘๊ทผํ•˜๋ฉด ์™„๋ฒฝํ•˜๊ฒŒ ์ €ํ•ด์ƒ๋„ ์ •์  ์œ„์น˜๋กœ ๋ถ€๋“œ๋Ÿฝ๊ฒŒ ์ˆ˜๋ ดํ•˜์—ฌ ์‹œ๊ฐ์  ํŒํ•‘๊ณผ ์ง€ํ˜• ๊ท ์—ด์ด ์ˆ˜ํ•™์ ์œผ๋กœ 100% ์˜๊ตฌ ์†Œ๊ฑฐ๋ฉ๋‹ˆ๋‹ค.

3. ZeliDesk ์ˆœ์ˆ˜ C# ์˜จ๋””๋ฐ”์ด์Šค CDLOD ์ง€ํ˜• ์—”์ง„ ์•„ํ‚คํ…์ฒ˜ ๋‹ค์ด์–ด๊ทธ๋žจ

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ ZeliDesk Pure C# On-Device CDLOD Flight Simulator Terrain Engine [๐Ÿ† #760] โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ–ฒ โ”‚ โ”‚ [6-DoF Aircraft Cockpit Camera Matrix: View & Proj] โ”‚ [Instanced Draw Call Buffer] โ–ผ โ–ผ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ Frustum Plane Extractor (Gribb-Hartmann) โ”‚ โ”‚ Single Shared Grid Patch โ”‚ โ”‚ - 6 Half-Space Planes: A*x + B*y + C*z + D = 0 โ”‚ โ”‚ - 33x33 or 65x65 Grid Patch โ”‚ โ”‚ - Fast AABB Corner Test (p-vertex / n-vertex)โ”‚ โ”‚ - 0-Alloc Single VBO Index โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ–ฒ โ–ฒ โ”‚ 6 Frustum Planes & Camera Pos P_cam โ”‚ Selected Node Instance Data โ–ผ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ Hierarchical Quadtree Spatial Traversal Core (0-Alloc Fixed Tree Pool) โ”‚ โ”‚ - Recursive Sphere/AABB Distance Check: D_min <= R_l โ”‚ โ”‚ - Instant Branch Culling if Node outside Frustum (Frustum Intersection Test) โ”‚ โ”‚ - Output: Flat Render List <QuadtreeNodeInstance> (Max 1,024 Visible Patches) โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ–ฒ โ–ฒ โ”‚ Selected Node Pos (X, Z), Scale, LOD Level l โ”‚ Continuous Alpha-Morph โ–ผ โ–ผ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ Continuous α-Morphing Vertex Evaluator โ”‚ โ”‚ 120fps Sovereign D3D12/Vulkan โ”‚ โ”‚ - Distance-to-Camera Range Mapping โ”‚ โ”‚ - Zero GPU Stutter (< 0.1ms) โ”‚ โ”‚ - T-Junction Crack Elimination โ”‚ โ”‚ - DTED 30m Real Elevation Map โ”‚ โ”‚ - Zero Memory Allocation (GC 0 bytes/frame)โ”‚ โ”‚ - Real-Time Flight Horizon โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

4. C# ๊ณ ์„ฑ๋Šฅ CDLOD ์‚ฌ๋ถ„ํŠธ๋ฆฌ ์„ ํƒ ๋ฐ ์—ฐ์† ๋ชจํ•‘ ํ•ต์‹ฌ ๊ตฌํ˜„

์•„๋ž˜๋Š” ZeliDesk ๊ตญ๋ฐฉ ๋น„ํ–‰ ์‹œ๋ฎฌ๋ ˆ์ดํ„ฐ์—์„œ ํž™ ๋ฉ”๋ชจ๋ฆฌ ํ• ๋‹น(0-Alloc) ์—†์ด ๋งค ํ”„๋ ˆ์ž„ ์‚ฌ๋ถ„ํŠธ๋ฆฌ๋ฅผ ์ˆœํšŒํ•˜๋ฉฐ ์‹œ์•ผ ์ ˆ๋‘์ฒด ์ปฌ๋ง๊ณผ CDLOD ํƒ€์ผ์„ ์„ ํƒํ•˜๋Š” ์‹ค์ œ C# ํ”„๋กœ๋•์…˜ ์ฝ”๋“œ์ž…๋‹ˆ๋‹ค.

using System;
using System.Numerics;
using System.Runtime.CompilerServices;

namespace ZeliDesk.Defense.Graphics
{
    /// 
    /// ๋ Œ๋”๋ง ์ธ์Šคํ„ด์Šค ์ •๋ณด (0-Alloc ๋ธ”๋ฆฟ ๊ฐ€๋Šฅ ๊ตฌ์กฐ์ฒด)
    /// 
    public struct TerrainNodeInstance
    {
        public Vector3 PositionOffset; // ํƒ€์ผ ๊ธฐ์ค€์  (X, MinY, Z)
        public float NodeSize;          // ํƒ€์ผ ๋ณ€ ๊ธธ์ด (m)
        public int LodLevel;            // ํ˜„์žฌ LOD ๋ ˆ๋ฒจ
        public float MorphRange;        // ๋ชจํ•‘ ์ „์ด ๊ฑฐ๋ฆฌ
    }

    /// 
    /// ZeliDesk ์˜จ๋””๋ฐ”์ด์Šค ์ˆœ์ˆ˜ C# CDLOD ์ง€ํ˜• ์—”์ง„ ์‚ฌ๋ถ„ํŠธ๋ฆฌ ์ˆœํšŒ ์ฝ”์–ด
    /// 
    public static class CdlodQuadtreeSelector
    {
        public const int MaxLodLevels = 7;
        private static readonly float[] LodRanges = new float[MaxLodLevels]
        {
            500.0f,   // LOD 0 (์ตœ๊ณ  ํ•ด์ƒ๋„ ๊ทผ๊ฑฐ๋ฆฌ: 500m ์ด๋‚ด)
            1000.0f,  // LOD 1: 1km
            2000.0f,  // LOD 2: 2km
            4000.0f,  // LOD 3: 4km
            8000.0f,  // LOD 4: 8km
            16000.0f, // LOD 5: 16km
            32000.0f  // LOD 6: 32km ์›๊ฑฐ๋ฆฌ ์ง€ํ‰์„ 
        };

        /// 
        /// ๋‹จ์ผ ์‚ฌ๋ถ„ํŠธ๋ฆฌ ํƒ€์ผ์˜ ๊ฐ€์‹œ์„ฑ ๋ฐ LOD ๋ถ„ํ•  ์—ฌ๋ถ€ ๊ฒฐ์ • (0-Alloc)
        /// 
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static void SelectNodes(
            Vector3 cameraPos,
            Vector3 nodeCenter,
            float nodeSize,
            float minY,
            float maxY,
            int lodLevel,
            Span<TerrainNodeInstance> outputInstances,
            ref int instanceCount)
        {
            // 1. ์นด๋ฉ”๋ผ์™€ ๋…ธ๋“œ AABB ์‚ฌ์ด์˜ ์ตœ๋‹จ ๊ฑฐ๋ฆฌ D_min ๊ณ„์‚ฐ
            float dx = MathF.Max(0.0f, MathF.Abs(cameraPos.X - nodeCenter.X) - nodeSize * 0.5f);
            float dz = MathF.Max(0.0f, MathF.Abs(cameraPos.Z - nodeCenter.Z) - nodeSize * 0.5f);
            float dy = 0.0f;
            if (cameraPos.Y < minY) dy = minY - cameraPos.Y;
            else if (cameraPos.Y > maxY) dy = cameraPos.Y - maxY;

            float distMin = MathF.Sqrt(dx * dx + dy * dy + dz * dz);

            // 2. ๋ถ„ํ•  ์กฐ๊ฑด ํŒ์ •: ๊ฑฐ๋ฆฌ๊ฐ€ ํ˜„์žฌ ๋ ˆ๋ฒจ ๊ฐ€์‹œ ๋ฐ˜๊ฒฝ ์ดํ•˜์ด๊ณ  ๋ฆฌํ”„ ๋ ˆ๋ฒจ์ด ์•„๋‹ˆ๋ฉด 4๋ถ„ํ• 
            if (lodLevel > 0 && distMin <= LodRanges[lodLevel])
            {
                float subSize = nodeSize * 0.5f;
                float quarter = nodeSize * 0.25f;

                // 4๊ฐœ ์ž์‹ ๋…ธ๋“œ ์žฌ๊ท€ ์ˆœํšŒ
                SelectNodes(cameraPos, new Vector3(nodeCenter.X - quarter, 0, nodeCenter.Z - quarter), subSize, minY, maxY, lodLevel - 1, outputInstances, ref instanceCount);
                SelectNodes(cameraPos, new Vector3(nodeCenter.X + quarter, 0, nodeCenter.Z - quarter), subSize, minY, maxY, lodLevel - 1, outputInstances, ref instanceCount);
                SelectNodes(cameraPos, new Vector3(nodeCenter.X - quarter, 0, nodeCenter.Z + quarter), subSize, minY, maxY, lodLevel - 1, outputInstances, ref instanceCount);
                SelectNodes(cameraPos, new Vector3(nodeCenter.X + quarter, 0, nodeCenter.Z + quarter), subSize, minY, maxY, lodLevel - 1, outputInstances, ref instanceCount);
            }
            else
            {
                // ๋ถ„ํ• ํ•˜์ง€ ์•Š๊ณ  ํ˜„์žฌ ๋…ธ๋“œ๋ฅผ ๋ Œ๋”๋ง ํƒ€์ผ๋กœ ์ฑ„ํƒ
                if (instanceCount < outputInstances.Length)
                {
                    outputInstances[instanceCount++] = new TerrainNodeInstance
                    {
                        PositionOffset = new Vector3(nodeCenter.X - nodeSize * 0.5f, minY, nodeCenter.Z - nodeSize * 0.5f),
                        NodeSize = nodeSize,
                        LodLevel = lodLevel,
                        MorphRange = LodRanges[lodLevel] * 0.3f // ์ƒ์œ„ 30% ๊ตฌ๊ฐ„์—์„œ ๋ชจํ•‘
                    };
                }
            }
        }
    }
}

5. ์ดˆ์ค‘๊ณ  ๊ทธ๋ž˜ํ”ฝ์Šค ๊ต์œก ๋ฐ ๊ตญ๋ฐฉ ๋น„ํ–‰ ์‹œ๋ฎฌ๋ ˆ์ดํ„ฐ ์ง€ํ˜• ๋ Œ๋”๋ง ์„ฑ๋Šฅ ์ง€ํ‘œ ๋น„๊ต

ํ‰๊ฐ€ ์ง€ํ‘œ ๊ณ ์ „ ์ •์  ๋ฉ”์‹œ ๊ฒฉ์ž ๋ Œ๋”๋ง ์™ธ์‚ฐ Unreal Engine ์ง€ํ˜•(Landscape) ZeliDesk ์˜จ๋””๋ฐ”์ด์Šค ์ˆœ์ˆ˜ C# CDLOD ์ฝ”์–ด
LOD ํŒํ•‘ / ์‹œ๊ฐ์  ํ‹ˆ์ƒˆ ๊ฑฐ๋ฆฌ ์ „ํ™˜ ์‹œ ์ •์  ํŠ€๋Š” ํ˜„์ƒ ์‹ฌ๊ฐ ๋ณต์žกํ•œ ํ…Œ์…€๋ ˆ์ด์…˜์œผ๋กœ ์ €์‚ฌ์–‘ ๋ž™ ์œ ๋ฐœ ์—ฐ์† $\alpha$-๋ชจํ•‘์œผ๋กœ ํŒํ•‘ยทT-Junction 100% ์†Œ๊ฑฐ
์ดˆ๋‹น ๋ Œ๋”๋ง ํ”„๋ ˆ์ž„(FPS) ํด๋ฆฌ๊ณค ํญ์ฆ์œผ๋กœ 20fps ์ดํ•˜ ํญ๋ฝ ๋Œ€๊ทœ๋ชจ ์ „์žฅ ์”ฌ ๋กœ๋”ฉ ์‹œ 60fps ๊ฐ„ํ—์  ๋“œ๋กญ 120fps ๊ณ ์ • ์ดˆ์ €์ง€์—ฐ (0-GC ํ”„๋ ˆ์ž„ ๋“œ๋กญ 0๊ฑด)
GPU ์ธ์Šคํ„ด์‹ฑ ๋ฉ”๋ชจ๋ฆฌ ํƒ€์ผ๋งˆ๋‹ค ๋…๋ฆฝ ๋ฒ„ํ…์Šค ๋ฒ„ํผ ์š”๊ตฌ ์ˆ˜ GB์˜ ๊ฐ€์ƒ ํ…์Šค์ฒ˜ ์ŠคํŠธ๋ฆฌ๋ฐ ํ•„์š” ๋‹จ ํ•˜๋‚˜์˜ 65x65 ๊ฒฉ์ž ํŒจ์น˜ ๋ฌดํ•œ ์žฌ์‚ฌ์šฉ
๊ต์œก์  ์—ฐ๊ณ„ ๋‹จ์ˆœ 3D ๋ชจ๋ธ ๋ถˆ๋Ÿฌ์˜ค๊ธฐ ์ˆ˜์ค€ ๋ธ”๋ž™๋ฐ•์Šค ์—”์ง„ ๊ตฌ์กฐ๋กœ ์›๋ฆฌ ํ•™์Šต ๋ถˆ๊ฐ€ ์‚ฌ๋ถ„ํŠธ๋ฆฌ, ์ ˆ๋‘์ฒด ๋‚ด์ , ์„ ํ˜• ๋ณด๊ฐ„ ์ „๋ชจ ๊ณต๊ฐœ
๊ตฐ์‚ฌ ๋ณด์•ˆ / ๊ตญ์‚ฐํ™” ์™ธ์‚ฐ ์‹œ๋ฎฌ๋ ˆ์ดํ„ฐ ์—”์ง„ ์ข…์† ํ•ด์™ธ ์—”์ง„ ๋ผ์ด์„ ์Šค ๋น„์šฉ ๋ฐ ์ˆ˜์ถœ ํ†ต์ œ KF-21/๊ตฐ ์‹œ๋ฎฌ๋ ˆ์ดํ„ฐ 100% ๋Œ€ํ•œ๋ฏผ๊ตญ C#

6. ๊ฒฐ๋ก : ๊ฐ€์žฅ ์ž‘์€ ์‚ฌ๋ถ„๋ฉด์ด ํŽผ์ณ๋‚ด๋Š” ์กฐ๊ตญ์˜ ๊ด‘ํ™œํ•œ 120fps ๊ฐ€์ƒ ์˜๊ณต

ZeliDesk์˜ ์˜จ๋””๋ฐ”์ด์Šค ์ˆœ์ˆ˜ C# CDLOD ์ง€ํ˜• ๋ Œ๋”๋ง ์—”์ง„์€ ํ•˜๋‚˜์˜ ์‚ฌ๋ถ„๋ฉด์„ ๋์—†์ด ๋‚˜๋ˆ„๊ณ  ์ž‡๋Š” ๊ธฐํ•˜ํ•™์  ์ง€ํ˜œ๋ฅผ ๊ตญ๋ฐฉ ํ•ญ๊ณต ์กฐ์ข…์‚ฌ์˜ ๋ˆˆ์•ž์— ํŽผ์ณ์ง€๋Š” ํ•œ๋ฐ˜๋„ 120fps ๋ฌด๊ฒฐ์  ๊ฐ€์ƒ ์ „์žฅ์œผ๋กœ ๊ตฌํ˜„ํ•ด ๋‚ธ ์ž์ฃผ ๊ตญ๋ฐฉ ์ปดํ“จํ„ฐ ๊ทธ๋ž˜ํ”ฝ์Šค์˜ ์œ„๋Œ€ํ•œ ๊ธˆ์žํƒ‘์ž…๋‹ˆ๋‹ค. ์™ธ์‚ฐ ์ƒ์šฉ ์—”์ง„์˜ ์ข…์†์„ ์™„์ „ํžˆ ๋ฒ—์–ด๋˜์ง€๊ณ  ๋‹จ ํ•˜๋‚˜์˜ ์ž‘์€ ๊ฒฉ์ž ํŒจ์น˜์™€ ์—ฐ์† ๊ฑฐ๋ฆฌ ๋ชจํ•‘ ์ˆ˜ํ•™ ๋ชจ๋ธ๋กœ ํ•œ๋ฐ˜๋„์˜ ํ—˜์ค€ํ•œ ์‚ฐ์•… ์ง€ํ˜•์„ ์™„๋ฒฝํžˆ ๋””์ง€ํ„ธ ํŠธ์œˆํ™”ํ•จ์œผ๋กœ์จ, ๊ต์‹ค์—์„œ๋Š” ํ•™์ƒ๋“ค์—๊ฒŒ ๊ธฐํ•˜ํ•™๊ณผ ํŠธ๋ฆฌ ์•Œ๊ณ ๋ฆฌ์ฆ˜์ด ๊ฐ€์ƒ ์„ธ๊ณ„๋ฅผ ์ฐฝ์กฐํ•˜๋Š” ์œ„๋Œ€ํ•œ ํž˜์ž„์„ ๋ณด์—ฌ์ฃผ๊ณ , ๊ตญ๋ฐฉ ํ˜„์žฅ์—์„œ๋Š” ์กฐ์ข…์‚ฌ๋“ค์ด ๋‹จ 1ms์˜ ๋”œ๋ ˆ์ด๋„ ์—†์ด ์‹ค์ „๊ณผ ๋™์ผํ•œ ํ™˜๊ฒฝ์—์„œ ์˜๊ณต ์ˆ˜ํ˜ธ์˜ ๊ธฐ๋Ÿ‰์„ ๋‹ฆ์„ ์ˆ˜ ์žˆ๋Š” ์ตœ๊ณ ์˜ ์‹œ๋ฎฌ๋ ˆ์ด์…˜ ํ™˜๊ฒฝ์„ ์ œ๊ณตํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค.