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 ์งํ ์์ง ์ํคํ ์ฒ ๋ค์ด์ด๊ทธ๋จ
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์ ๋๋ ์ด๋ ์์ด ์ค์ ๊ณผ ๋์ผํ ํ๊ฒฝ์์ ์๊ณต ์ํธ์ ๊ธฐ๋์ ๋ฆ์ ์ ์๋ ์ต๊ณ ์ ์๋ฎฌ๋ ์ด์ ํ๊ฒฝ์ ์ ๊ณตํ ๊ฒ์ ๋๋ค.