๐ŸŽฎ ์–ธ๋ฆฌ์–ผ ์—”์ง„์˜ LOD ์‹œ์Šคํ…œ ๊ตฌํ˜„ํ•˜๊ธฐ ๐Ÿš€

์ฝ˜ํ…์ธ  ๋Œ€ํ‘œ ์ด๋ฏธ์ง€ - ๐ŸŽฎ ์–ธ๋ฆฌ์–ผ ์—”์ง„์˜ LOD ์‹œ์Šคํ…œ ๊ตฌํ˜„ํ•˜๊ธฐ ๐Ÿš€

 

 

์•ˆ๋…•, ์นœ๊ตฌ๋“ค! ์˜ค๋Š˜์€ ๊ฒŒ์ž„ ๊ฐœ๋ฐœ์˜ ํ•ต์‹ฌ ๊ธฐ์ˆ  ์ค‘ ํ•˜๋‚˜์ธ ์–ธ๋ฆฌ์–ผ ์—”์ง„์˜ ๋ ˆ๋ฒจ ์˜ค๋ธŒ ๋””ํ…Œ์ผ(LOD) ์‹œ์Šคํ…œ์— ๋Œ€ํ•ด ์žฌ๋ฏธ์žˆ๊ฒŒ ํŒŒํ—ค์ณ๋ณผ ๊ฑฐ์•ผ. ๐Ÿ˜Ž ์ด ๊ธฐ์ˆ ์€ ๊ฒŒ์ž„์˜ ์„ฑ๋Šฅ๊ณผ ๊ทธ๋ž˜ํ”ฝ ํ€„๋ฆฌํ‹ฐ๋ฅผ ๋™์‹œ์— ์žก๋Š” ๋งˆ๋ฒ• ๊ฐ™์€ ๋…€์„์ด์ง€. ์šฐ๋ฆฌ๊ฐ€ ๋งŒ๋“  ๊ฒŒ์ž„์ด ์–ด๋–ค ๊ธฐ๊ธฐ์—์„œ๋“  ๋ถ€๋“œ๋Ÿฝ๊ฒŒ ๋Œ์•„๊ฐ€๊ฒŒ ํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด, LOD๋Š” ๊ผญ ์•Œ์•„๋‘ฌ์•ผ ํ•  ์นœ๊ตฌ๋ผ๊ณ !

๊ทธ๋Ÿผ ์ด์ œ๋ถ€ํ„ฐ LOD์˜ ์„ธ๊ณ„๋กœ ํ•จ๊ป˜ ๋– ๋‚˜๋ณผ๊นŒ? ๐Ÿš€

๐Ÿง LOD๊ฐ€ ๋ญ๊ธธ๋ž˜?

LOD, ์ฆ‰ ๋ ˆ๋ฒจ ์˜ค๋ธŒ ๋””ํ…Œ์ผ์€ ๋ง ๊ทธ๋Œ€๋กœ '๋””ํ…Œ์ผ์˜ ๋‹จ๊ณ„'๋ฅผ ์˜๋ฏธํ•ด. ๊ฒŒ์ž„์—์„œ ๋ฌผ์ฒด๊ฐ€ ์นด๋ฉ”๋ผ๋กœ๋ถ€ํ„ฐ ๋ฉ€์–ด์งˆ์ˆ˜๋ก ๋œ ์ž์„ธํ•œ ๋ชจ๋ธ์„ ์‚ฌ์šฉํ•˜๋Š” ๊ธฐ์ˆ ์ด์•ผ. ์‰ฝ๊ฒŒ ๋งํ•ด, ๋ฉ€๋ฆฌ ์žˆ๋Š” ๊ฑด ๋Œ€์ถฉ ๊ทธ๋ ค๋„ ํ‹ฐ๊ฐ€ ์•ˆ ๋‚˜๋‹ˆ๊นŒ ๊ฐ„๋‹จํ•˜๊ฒŒ ๊ทธ๋ฆฌ์ž๋Š” ๊ฑฐ์ง€!

๐ŸŽจ LOD์˜ ํ•ต์‹ฌ ์•„์ด๋””์–ด: ๋ฉ€๋ฆฌ ์žˆ๋Š” ๊ฑด ๋Œ€์ถฉ ๊ทธ๋ ค๋„ ๊ดœ์ฐฎ์•„!

์˜ˆ๋ฅผ ๋“ค์–ด๋ณผ๊นŒ? ๐Ÿค” ์šฐ๋ฆฌ๊ฐ€ ๊ฒŒ์ž„์—์„œ ๋‚˜๋ฌด๋ฅผ ๊ทธ๋ฆฐ๋‹ค๊ณ  ์ƒ๊ฐํ•ด๋ณด์ž. ํ”Œ๋ ˆ์ด์–ด ๋ฐ”๋กœ ์•ž์— ์žˆ๋Š” ๋‚˜๋ฌด๋Š” ์•„์ฃผ ์ž์„ธํ•˜๊ฒŒ ๊ทธ๋ฆด ๊ฑฐ์•ผ. ์žŽ์‚ฌ๊ท€ ํ•˜๋‚˜ํ•˜๋‚˜, ๋‚˜๋ญ‡๊ฐ€์ง€์˜ ๊ตด๊ณก๊นŒ์ง€ ๋‹ค ํ‘œํ˜„ํ•˜๊ฒ ์ง€. ํ•˜์ง€๋งŒ ๋ฉ€๋ฆฌ ์žˆ๋Š” ๋‚˜๋ฌด๋Š”? ๊ทธ๋ƒฅ ์ดˆ๋ก์ƒ‰ ๋ฉ์–ด๋ฆฌ๋กœ ๋ณด์—ฌ๋„ ์ถฉ๋ถ„ํ•  ๊ฑฐ์•ผ!

LOD ๊ฐœ๋… ์„ค๋ช… ๊ทธ๋ฆผ ๊ฐ€๊นŒ์šด ๋‚˜๋ฌด (๊ณ ํ•ด์ƒ๋„) ๋จผ ๋‚˜๋ฌด (์ €ํ•ด์ƒ๋„) ๋จผ ๋‚˜๋ฌด (์ €ํ•ด์ƒ๋„)

์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ๋ญ๊ฐ€ ์ข‹์„๊นŒ? ๐Ÿค“

  • ๊ฒŒ์ž„์ด ๋” ๋นจ๋ฆฌ ๋Œ์•„๊ฐ€! (์„ฑ๋Šฅ ํ–ฅ์ƒ)
  • ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ๋œ ์จ! (์ตœ์ ํ™”)
  • ๋ฉ€๋ฆฌ ์žˆ๋Š” ๊ฑด ์–ด์ฐจํ”ผ ์ž˜ ์•ˆ ๋ณด์ด๋‹ˆ๊นŒ, ํ€„๋ฆฌํ‹ฐ๋Š” ๊ทธ๋Œ€๋กœ!

์žฌ๋Šฅ๋„ท์—์„œ 3D ๋ชจ๋ธ๋ง ์ „๋ฌธ๊ฐ€๋ฅผ ์ฐพ์•„ LOD ๋ชจ๋ธ์„ ๋งŒ๋“ค์–ด๋‹ฌ๋ผ๊ณ  ํ•  ์ˆ˜๋„ ์žˆ๊ฒ ๋„ค. ๊ทธ๋“ค์˜ ์žฌ๋Šฅ์œผ๋กœ ์šฐ๋ฆฌ ๊ฒŒ์ž„์˜ ์„ฑ๋Šฅ์„ ํ•œ์ธต ๋” ๋Œ์–ด์˜ฌ๋ฆด ์ˆ˜ ์žˆ์„ ๊ฑฐ์•ผ! ๐ŸŒŸ

๐Ÿ› ๏ธ ์–ธ๋ฆฌ์–ผ ์—”์ง„์—์„œ LOD ์‹œ์Šคํ…œ ๊ตฌํ˜„ํ•˜๊ธฐ

์ž, ์ด์ œ ์‹ค์ œ๋กœ ์–ธ๋ฆฌ์–ผ ์—”์ง„์—์„œ LOD๋ฅผ ์–ด๋–ป๊ฒŒ ๊ตฌํ˜„ํ•˜๋Š”์ง€ ์•Œ์•„๋ณผ๊นŒ? ๐Ÿ•ต๏ธโ€โ™‚๏ธ ๊ฑฑ์ • ๋งˆ, ์ƒ๊ฐ๋ณด๋‹ค ์–ด๋ ต์ง€ ์•Š์•„!

1. ์Šคํƒœํ‹ฑ ๋ฉ”์‹œ์— LOD ์„ค์ •ํ•˜๊ธฐ

์–ธ๋ฆฌ์–ผ ์—”์ง„์—์„œ ๊ฐ€์žฅ ๊ธฐ๋ณธ์ ์ธ LOD ๊ตฌํ˜„ ๋ฐฉ๋ฒ•์€ ์Šคํƒœํ‹ฑ ๋ฉ”์‹œ์— ์ง์ ‘ LOD๋ฅผ ์„ค์ •ํ•˜๋Š” ๊ฑฐ์•ผ.

  1. ์ฝ˜ํ…์ธ  ๋ธŒ๋ผ์šฐ์ €์—์„œ LOD๋ฅผ ์ ์šฉํ•  ์Šคํƒœํ‹ฑ ๋ฉ”์‹œ๋ฅผ ๋”๋ธ” ํด๋ฆญํ•ด ์—ด์–ด.
  2. ์Šคํƒœํ‹ฑ ๋ฉ”์‹œ ์—๋””ํ„ฐ ์ฐฝ์—์„œ 'LOD ์„ค์ •'์„ ์ฐพ์•„.
  3. '์ž๋™ LOD ์ƒ์„ฑ' ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜๋ฉด ์–ธ๋ฆฌ์–ผ ์—”์ง„์ด ์ž๋™์œผ๋กœ LOD๋ฅผ ๋งŒ๋“ค์–ด์ค˜!

๐Ÿ” ํ”„๋กœ ํŒ: ์ž๋™ ์ƒ์„ฑ๋œ LOD๊ฐ€ ๋งˆ์Œ์— ๋“ค์ง€ ์•Š๋Š”๋‹ค๋ฉด, ๊ฐ LOD ๋ ˆ๋ฒจ์„ ์ˆ˜๋™์œผ๋กœ ์กฐ์ •ํ•  ์ˆ˜ ์žˆ์–ด. ์‚ผ๊ฐํ˜• ์ˆ˜๋ฅผ ์ค„์ด๊ฑฐ๋‚˜ ํ…์Šค์ฒ˜ ํ•ด์ƒ๋„๋ฅผ ๋‚ฎ์ถ”๋Š” ๋“ฑ์˜ ๋ฐฉ๋ฒ•์œผ๋กœ ์ตœ์ ํ™”ํ•  ์ˆ˜ ์žˆ์ง€!

2. ๋ธ”๋ฃจํ”„๋ฆฐํŠธ๋กœ ๋™์  LOD ๊ตฌํ˜„ํ•˜๊ธฐ

์ข€ ๋” ๊ณ ๊ธ‰ ๊ธฐ์ˆ ์„ ์›ํ•œ๋‹ค๋ฉด, ๋ธ”๋ฃจํ”„๋ฆฐํŠธ๋ฅผ ์‚ฌ์šฉํ•ด ๋™์ ์œผ๋กœ LOD๋ฅผ ์ œ์–ดํ•  ์ˆ˜ ์žˆ์–ด. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ๊ฒŒ์ž„ ํ”Œ๋ ˆ์ด ์ค‘์— ์ƒํ™ฉ์— ๋”ฐ๋ผ LOD๋ฅผ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ์ง€.


// LOD ๋ ˆ๋ฒจ์„ ๋™์ ์œผ๋กœ ์„ค์ •ํ•˜๋Š” ๋ธ”๋ฃจํ”„๋ฆฐํŠธ ์˜ˆ์‹œ
Event BeginPlay
{
    // ํ˜„์žฌ ์•กํ„ฐ์™€ ์นด๋ฉ”๋ผ ์‚ฌ์ด์˜ ๊ฑฐ๋ฆฌ ๊ณ„์‚ฐ
    float Distance = GetDistanceTo(PlayerCamera);
    
    // ๊ฑฐ๋ฆฌ์— ๋”ฐ๋ผ LOD ๋ ˆ๋ฒจ ์„ค์ •
    if (Distance > 1000)
    {
        SetForcedLODModel(2);
    }
    else if (Distance > 500)
    {
        SetForcedLODModel(1);
    }
    else
    {
        SetForcedLODModel(0);
    }
}

์ด ์ฝ”๋“œ๋Š” ํ”Œ๋ ˆ์ด์–ด ์นด๋ฉ”๋ผ์™€์˜ ๊ฑฐ๋ฆฌ์— ๋”ฐ๋ผ LOD ๋ ˆ๋ฒจ์„ ๋™์ ์œผ๋กœ ๋ณ€๊ฒฝํ•ด. ๋ฉ€๋ฆฌ ์žˆ์„ ๋•Œ๋Š” ๋œ ์ƒ์„ธํ•œ ๋ชจ๋ธ์„, ๊ฐ€๊นŒ์ด ์žˆ์„ ๋•Œ๋Š” ๋” ์ƒ์„ธํ•œ ๋ชจ๋ธ์„ ์‚ฌ์šฉํ•˜๊ฒŒ ๋˜๋Š” ๊ฑฐ์ง€.

3. ๋จธํ‹ฐ๋ฆฌ์–ผ์—์„œ LOD ํ™œ์šฉํ•˜๊ธฐ

LOD๋Š” ๋ฉ”์‹œ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ๋จธํ‹ฐ๋ฆฌ์–ผ์—์„œ๋„ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ์–ด. ๋ฉ€๋ฆฌ ์žˆ๋Š” ์˜ค๋ธŒ์ ํŠธ์—๋Š” ๋” ๊ฐ„๋‹จํ•œ ์…ฐ์ด๋”๋ฅผ ์ ์šฉํ•˜๋Š” ์‹์ด์ง€.


// ๋จธํ‹ฐ๋ฆฌ์–ผ์—์„œ LOD ๋ ˆ๋ฒจ์— ๋”ฐ๋ผ ๋‹ค๋ฅธ ํ…์Šค์ฒ˜ ์‚ฌ์šฉํ•˜๊ธฐ
if (LOD_SWITCH(2))
{
    Diffuse = Texture_LowRes;
}
else
{
    Diffuse = Texture_HighRes;
}

์ด๋ ‡๊ฒŒ ํ•˜๋ฉด LOD ๋ ˆ๋ฒจ์ด 2 ์ด์ƒ์ผ ๋•Œ(์ฆ‰, ๋ฉ€๋ฆฌ ์žˆ์„ ๋•Œ) ์ €ํ•ด์ƒ๋„ ํ…์Šค์ฒ˜๋ฅผ ์‚ฌ์šฉํ•˜๊ณ , ๊ทธ ์™ธ์—๋Š” ๊ณ ํ•ด์ƒ๋„ ํ…์Šค์ฒ˜๋ฅผ ์‚ฌ์šฉํ•˜๊ฒŒ ๋ผ.

LOD ๋ ˆ๋ฒจ์— ๋”ฐ๋ฅธ ํ…์Šค์ฒ˜ ๋ณ€ํ™” ๊ณ ํ•ด์ƒ๋„ ํ…์Šค์ฒ˜ ์ €ํ•ด์ƒ๋„ ํ…์Šค์ฒ˜ LOD ์ „ํ™˜ ์ง€์ 

์ด๋Ÿฐ ์‹์œผ๋กœ LOD๋ฅผ ๊ตฌํ˜„ํ•˜๋ฉด, ๊ฒŒ์ž„์˜ ์„ฑ๋Šฅ๊ณผ ๊ทธ๋ž˜ํ”ฝ ํ€„๋ฆฌํ‹ฐ ์‚ฌ์ด์—์„œ ์™„๋ฒฝํ•œ ๊ท ํ˜•์„ ์žก์„ ์ˆ˜ ์žˆ์–ด. ๐Ÿ‘Œ

์žฌ๋Šฅ๋„ท์—์„œ ์–ธ๋ฆฌ์–ผ ์—”์ง„ ์ „๋ฌธ๊ฐ€๋ฅผ ์ฐพ์•„ LOD ์‹œ์Šคํ…œ ๊ตฌํ˜„์— ๋Œ€ํ•œ ์กฐ์–ธ์„ ๊ตฌํ•ด๋ณด๋Š” ๊ฒƒ๋„ ์ข‹์€ ๋ฐฉ๋ฒ•์ด์•ผ. ๊ทธ๋“ค์˜ ๊ฒฝํ—˜๊ณผ ๋…ธํ•˜์šฐ๋กœ ๋” ํšจ์œจ์ ์ธ LOD ์‹œ์Šคํ…œ์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ์„ ๊ฑฐ์•ผ!

๐ŸŽฏ LOD ์‹œ์Šคํ…œ์˜ ์ตœ์ ํ™” ํŒ

LOD ์‹œ์Šคํ…œ์„ ๊ตฌํ˜„ํ–ˆ๋‹ค๊ณ  ํ•ด์„œ ๋์ด ์•„๋‹ˆ์•ผ. ๋” ํšจ์œจ์ ์œผ๋กœ ๋งŒ๋“ค๊ธฐ ์œ„ํ•œ ๋ช‡ ๊ฐ€์ง€ ํŒ์„ ์•Œ๋ ค์ค„๊ฒŒ!

1. LOD ์ „ํ™˜ ์ง€์  ์กฐ์ •ํ•˜๊ธฐ

LOD ๋ ˆ๋ฒจ์ด ๋ฐ”๋€Œ๋Š” ์ง€์ ์„ ์ž˜ ์กฐ์ •ํ•˜๋Š” ๊ฒŒ ์ค‘์š”ํ•ด. ๋„ˆ๋ฌด ๊ฐ€๊นŒ์ด์—์„œ LOD๊ฐ€ ๋ฐ”๋€Œ๋ฉด ํ”Œ๋ ˆ์ด์–ด๊ฐ€ ๊ฐ‘์ž๊ธฐ ๋ชจ๋ธ์ด ๋ฐ”๋€Œ๋Š” ๊ฑธ ๋ˆˆ์น˜์ฑŒ ์ˆ˜ ์žˆ๊ฑฐ๋“ .


// LOD ์ „ํ™˜ ๊ฑฐ๋ฆฌ ์„ค์ • ์˜ˆ์‹œ
static FStaticMeshLODSettings LODSettings;
LODSettings.PercentTriangles[1] = 0.5f;  // LOD1์€ ์›๋ณธ์˜ 50% ์‚ผ๊ฐํ˜• ์‚ฌ์šฉ
LODSettings.PercentTriangles[2] = 0.25f; // LOD2๋Š” ์›๋ณธ์˜ 25% ์‚ผ๊ฐํ˜• ์‚ฌ์šฉ
LODSettings.ScreenSize[1] = 0.3f;        // ํ™”๋ฉด์˜ 30%๋ฅผ ์ฐจ์ง€ํ•  ๋•Œ LOD1๋กœ ์ „ํ™˜
LODSettings.ScreenSize[2] = 0.1f;        // ํ™”๋ฉด์˜ 10%๋ฅผ ์ฐจ์ง€ํ•  ๋•Œ LOD2๋กœ ์ „ํ™˜

์ด๋Ÿฐ ์‹์œผ๋กœ ๊ฐ LOD ๋ ˆ๋ฒจ์˜ ์ „ํ™˜ ์ง€์ ์„ ์„ธ๋ฐ€ํ•˜๊ฒŒ ์กฐ์ •ํ•  ์ˆ˜ ์žˆ์–ด. ๊ฒŒ์ž„์˜ ํŠน์„ฑ์— ๋งž๊ฒŒ ์ž˜ ์กฐ์ ˆํ•ด๋ณด์ž!

2. LOD ์‚ฌ์ด์˜ ๋ถ€๋“œ๋Ÿฌ์šด ์ „ํ™˜

LOD ๋ ˆ๋ฒจ์ด ๋ฐ”๋€” ๋•Œ ๊ฐ‘์ž๊ธฐ ํ™• ๋ฐ”๋€Œ๋ฉด ์–ด์ƒ‰ํ•ด ๋ณด์ผ ์ˆ˜ ์žˆ์–ด. ์ด๋ฅผ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•ด ๋””๋”๋ง(dithering)์ด๋‚˜ ์•ŒํŒŒ ํŽ˜์ด๋”ฉ(alpha fading) ๊ฐ™์€ ๊ธฐ์ˆ ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์ง€.


// ๋จธํ‹ฐ๋ฆฌ์–ผ์—์„œ LOD ์ „ํ™˜ ์‹œ ์•ŒํŒŒ ํŽ˜์ด๋”ฉ ์ ์šฉํ•˜๊ธฐ
float Alpha = 1.0 - abs(LODFade.x);
FinalColor.a *= Alpha;

์ด๋ ‡๊ฒŒ ํ•˜๋ฉด LOD ๋ ˆ๋ฒจ์ด ๋ฐ”๋€” ๋•Œ ๋ถ€๋“œ๋Ÿฝ๊ฒŒ ํŽ˜์ด๋“œ ์ธ/์•„์›ƒ ํšจ๊ณผ๊ฐ€ ์ ์šฉ๋ผ. ํ›จ์”ฌ ์ž์—ฐ์Šค๋Ÿฌ์›Œ ๋ณด์ด๊ฒ ์ง€?

LOD ์ „ํ™˜ ์‹œ ์•ŒํŒŒ ํŽ˜์ด๋”ฉ ํšจ๊ณผ LOD 0 LOD 1 ๋ถ€๋“œ๋Ÿฌ์šด ์ „ํ™˜ ๊ตฌ๊ฐ„

3. ํ•˜์ดํผ LOD ํ™œ์šฉํ•˜๊ธฐ

์•„์ฃผ ๋ฉ€๋ฆฌ ์žˆ๋Š” ์˜ค๋ธŒ์ ํŠธ๋“ค์€ ํ•˜์ดํผ LOD๋ผ๋Š” ๊ธฐ์ˆ ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์–ด. ์ด๊ฑด ์—ฌ๋Ÿฌ ์˜ค๋ธŒ์ ํŠธ๋ฅผ ํ•˜๋‚˜์˜ ๋‹จ์ˆœํ•œ ๋ชจ๋ธ๋กœ ํ•ฉ์น˜๋Š” ๊ฑฐ์•ผ.


// ํ•˜์ดํผ LOD ์ƒ์„ฑ ์˜ˆ์‹œ
UFUNCTION(BlueprintCallable, Category="LOD")
void CreateHyperLOD()
{
    TArray<aactor> ActorsToMerge;
    // ๋ฉ€๋ฆฌ ์žˆ๋Š” ์•กํ„ฐ๋“ค์„ ๋ฐฐ์—ด์— ์ถ”๊ฐ€
    
    UHyperLODMeshComponent* HyperLODMesh = NewObject<uhyperlodmeshcomponent>(this);
    HyperLODMesh->MergeActors(ActorsToMerge);
    HyperLODMesh->RegisterComponent();
}
</uhyperlodmeshcomponent></aactor>

์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ๋ฉ€๋ฆฌ ์žˆ๋Š” ์—ฌ๋Ÿฌ ๊ฑด๋ฌผ์ด๋‚˜ ๋‚˜๋ฌด๋“ค์„ ํ•˜๋‚˜์˜ ๋‹จ์ˆœํ•œ ๋ชจ๋ธ๋กœ ํ‘œํ˜„ํ•  ์ˆ˜ ์žˆ์–ด. ์„ฑ๋Šฅ์ด ์—„์ฒญ ์ข‹์•„์ง€๊ฒ ์ง€?

๐Ÿ’ก ๊ฟ€ํŒ: LOD ์‹œ์Šคํ…œ์„ ๊ตฌํ˜„ํ•  ๋•Œ๋Š” ํ•ญ์ƒ ์„ฑ๋Šฅ๊ณผ ๊ทธ๋ž˜ํ”ฝ ํ€„๋ฆฌํ‹ฐ ์‚ฌ์ด์˜ ๊ท ํ˜•์„ ๊ณ ๋ คํ•ด์•ผ ํ•ด. ์žฌ๋Šฅ๋„ท์—์„œ ๊ฒŒ์ž„ ์ตœ์ ํ™” ์ „๋ฌธ๊ฐ€์˜ ๋„์›€์„ ๋ฐ›์•„ ์ด ๊ท ํ˜•์„ ์™„๋ฒฝํ•˜๊ฒŒ ๋งž์ถœ ์ˆ˜ ์žˆ์„ ๊ฑฐ์•ผ!

๐Ÿ”ฌ LOD ์‹œ์Šคํ…œ์˜ ๊ณ ๊ธ‰ ๊ธฐ์ˆ ๋“ค

์ž, ์ด์ œ LOD์˜ ๊ธฐ๋ณธ์€ ์•Œ์•˜์œผ๋‹ˆ ์ข€ ๋” ๊ณ ๊ธ‰ ๊ธฐ์ˆ ๋“ค์„ ์‚ดํŽด๋ณผ๊นŒ? ์ด ๊ธฐ์ˆ ๋“ค์„ ๋งˆ์Šคํ„ฐํ•˜๋ฉด ๋„ค ๊ฒŒ์ž„์˜ ์„ฑ๋Šฅ๊ณผ ๊ทธ๋ž˜ํ”ฝ์„ ํ•œ ๋‹จ๊ณ„ ๋” ๋Œ์–ด์˜ฌ๋ฆด ์ˆ˜ ์žˆ์„ ๊ฑฐ์•ผ! ๐Ÿ˜Ž

1. ๋™์  LOD ์‹œ์Šคํ…œ

์ •์ ์ธ LOD ์‹œ์Šคํ…œ๋„ ์ข‹์ง€๋งŒ, ๊ฒŒ์ž„ ์ƒํ™ฉ์— ๋”ฐ๋ผ ๋™์ ์œผ๋กœ LOD๋ฅผ ์กฐ์ ˆํ•˜๋ฉด ๋” ์ข‹๊ฒ ์ง€? ์˜ˆ๋ฅผ ๋“ค์–ด, ์•ก์…˜์ด ๊ฒฉ๋ ฌํ•  ๋•Œ๋Š” LOD๋ฅผ ๋‚ฎ์ถ”๊ณ , ์กฐ์šฉํ•œ ์žฅ๋ฉด์—์„œ๋Š” LOD๋ฅผ ๋†’์ด๋Š” ์‹์œผ๋กœ ๋ง์ด์•ผ.


// ๋™์  LOD ์‹œ์Šคํ…œ ๊ตฌํ˜„ ์˜ˆ์‹œ
UFUNCTION(BlueprintCallable, Category="LOD")
void UpdateDynamicLOD()
{
    float GameSpeed = GetGameSpeed();
    float CPUUsage = GetCPUUsage();
    float GPUUsage = GetGPUUsage();
    
    if (GameSpeed > 1.5f || CPUUsage > 80.0f || GPUUsage > 90.0f)
    {
        // ๊ฒŒ์ž„์ด ๋น ๋ฅด๊ฒŒ ์ง„ํ–‰๋˜๊ฑฐ๋‚˜ ์‹œ์Šคํ…œ ๋ถ€ํ•˜๊ฐ€ ๋†’์„ ๋•Œ
        SetGlobalLODDistance(0.5f);  // LOD ๊ฑฐ๋ฆฌ๋ฅผ ์ค„์—ฌ ์„ฑ๋Šฅ ํ–ฅ์ƒ
    }
    else
    {
        // ๊ฒŒ์ž„์ด ๋Š๋ฆฌ๊ฒŒ ์ง„ํ–‰๋˜๊ฑฐ๋‚˜ ์‹œ์Šคํ…œ ์—ฌ์œ ๊ฐ€ ์žˆ์„ ๋•Œ
        SetGlobalLODDistance(1.0f);  // ๊ธฐ๋ณธ LOD ๊ฑฐ๋ฆฌ ์‚ฌ์šฉ
    }
}

์ด๋Ÿฐ ์‹์œผ๋กœ ๊ฒŒ์ž„์˜ ์ƒํ™ฉ๊ณผ ์‹œ์Šคํ…œ ์„ฑ๋Šฅ์„ ์‹ค์‹œ๊ฐ„์œผ๋กœ ์ฒดํฌํ•ด์„œ LOD๋ฅผ ์กฐ์ ˆํ•  ์ˆ˜ ์žˆ์–ด. ์™„์ „ ์Šค๋งˆํŠธํ•˜์ง€? ๐Ÿง 

2. ํ”„๋ก์‹œ ์ง€์˜ค๋ฉ”ํŠธ๋ฆฌ (Proxy Geometry)

ํ”„๋ก์‹œ ์ง€์˜ค๋ฉ”ํŠธ๋ฆฌ๋Š” ๋ณต์žกํ•œ ์˜ค๋ธŒ์ ํŠธ๋ฅผ ๋‹จ์ˆœํ•œ ํ˜•ํƒœ๋กœ ๋Œ€์ฒดํ•˜๋Š” ๊ธฐ์ˆ ์ด์•ผ. ์˜ˆ๋ฅผ ๋“ค์–ด, ๋ณต์žกํ•œ ๋‚˜๋ฌด ๋ชจ๋ธ์„ ๋ฉ€๋ฆฌ์„œ ๋ณผ ๋•Œ๋Š” ๊ฐ„๋‹จํ•œ ์›๊ธฐ๋‘ฅ์œผ๋กœ ๋Œ€์ฒดํ•˜๋Š” ๊ฑฐ์ง€.


// ํ”„๋ก์‹œ ์ง€์˜ค๋ฉ”ํŠธ๋ฆฌ ์„ค์ • ์˜ˆ์‹œ
UFUNCTION(BlueprintCallable, Category="LOD")
void SetupProxyGeometry(UStaticMeshComponent* OriginalMesh, UStaticMeshComponent* ProxyMesh)
{
    float Distance = GetDistanceToCamera(OriginalMesh);
    
    if (Distance > ProxyActivationDistance)
    {
        OriginalMesh->SetVisibility(false);
        ProxyMesh->SetVisibility(true);
    }
    else
    {
        OriginalMesh->SetVisibility(true);
        ProxyMesh->SetVisibility(false);
    }
}

์ด ์ฝ”๋“œ๋Š” ์นด๋ฉ”๋ผ์™€์˜ ๊ฑฐ๋ฆฌ์— ๋”ฐ๋ผ ์›๋ณธ ๋ฉ”์‹œ์™€ ํ”„๋ก์‹œ ๋ฉ”์‹œ๋ฅผ ์ „ํ™˜ํ•ด. ๋ฉ€๋ฆฌ ์žˆ์„ ๋•Œ๋Š” ๋‹จ์ˆœํ•œ ํ”„๋ก์‹œ๋ฅผ ๋ณด์—ฌ์ฃผ๊ณ , ๊ฐ€๊นŒ์ด ์˜ค๋ฉด ์ƒ์„ธํ•œ ์›๋ณธ์„ ๋ณด์—ฌ์ฃผ๋Š” ๊ฑฐ์•ผ.

ํ”„๋ก์‹œ ์ง€์˜ค๋ฉ”ํŠธ๋ฆฌ ๊ฐœ๋…๋„ ์ƒ์„ธํ•œ ๋‚˜๋ฌด ๋ชจ๋ธ ํ”„๋ก์‹œ ์ง€์˜ค๋ฉ”ํŠธ๋ฆฌ ๊ฑฐ๋ฆฌ์— ๋”ฐ๋ฅธ ์ „ํ™˜

3. ํ…Œ์…€๋ ˆ์ด์…˜ (Tessellation)์„ ํ™œ์šฉํ•œ LOD

ํ…Œ์…€๋ ˆ์ด์…˜์€ ํ•˜๋“œ์›จ์–ด ์ˆ˜์ค€์—์„œ ํด๋ฆฌ๊ณค์„ ๋™์ ์œผ๋กœ ๋Š˜๋ฆฌ๊ฑฐ๋‚˜ ์ค„์ด๋Š” ๊ธฐ์ˆ ์ด์•ผ. LOD์™€ ๊ฒฐํ•ฉํ•˜๋ฉด ์•„์ฃผ ๋ฉ‹์ง„ ํšจ๊ณผ๋ฅผ ๋‚ผ ์ˆ˜ ์žˆ์ง€!


// ํ…Œ์…€๋ ˆ์ด์…˜์„ ํ™œ์šฉํ•œ LOD ์„ค์ • ์˜ˆ์‹œ
// ๋จธํ‹ฐ๋ฆฌ์–ผ ์—๋””ํ„ฐ์—์„œ ์‚ฌ์šฉํ•˜๋Š” ์ฝ”๋“œ
float TessellationFactor = saturate((CameraDistance - MinDistance) / (MaxDistance - MinDistance));
TessellationFactor = 1.0 - TessellationFactor;
TessellationFactor = pow(TessellationFactor, 3);  // ๋น„์„ ํ˜• ๊ฐ์†Œ๋ฅผ ์œ„ํ•ด ์ œ๊ณฑ

Output = TessellationFactor * MaxTessellation;

์ด ์ฝ”๋“œ๋Š” ์นด๋ฉ”๋ผ์™€์˜ ๊ฑฐ๋ฆฌ์— ๋”ฐ๋ผ ํ…Œ์…€๋ ˆ์ด์…˜ ์ •๋„๋ฅผ ๋™์ ์œผ๋กœ ์กฐ์ ˆํ•ด. ๊ฐ€๊นŒ์ด ์žˆ์„ ๋•Œ๋Š” ๋” ๋งŽ์€ ํด๋ฆฌ๊ณค์œผ๋กœ ์ƒ์„ธํ•˜๊ฒŒ, ๋ฉ€์–ด์งˆ์ˆ˜๋ก ํด๋ฆฌ๊ณค ์ˆ˜๋ฅผ ์ค„์—ฌ ์„ฑ๋Šฅ์„ ์ตœ์ ํ™”ํ•˜๋Š” ๊ฑฐ์ง€.

๐Ÿš€ ๊ณ ๊ธ‰ ํŒ: ํ…Œ์…€๋ ˆ์ด์…˜๊ณผ LOD๋ฅผ ๊ฒฐํ•ฉํ•  ๋•Œ๋Š” ์„ฑ๋Šฅ์— ์ฃผ์˜ํ•ด์•ผ ํ•ด. ๋„ˆ๋ฌด ๋งŽ์€ ์˜ค๋ธŒ์ ํŠธ์— ํ…Œ์…€๋ ˆ์ด์…˜์„ ์ ์šฉํ•˜๋ฉด ์˜คํžˆ๋ ค ์„ฑ๋Šฅ์ด ๋–จ์–ด์งˆ ์ˆ˜ ์žˆ์œผ๋‹ˆ, ์ค‘์š”ํ•œ ์˜ค๋ธŒ์ ํŠธ์—๋งŒ ์„ ๋ณ„์ ์œผ๋กœ ์‚ฌ์šฉํ•˜๋Š” ๊ฒŒ ์ข‹์•„.

4. ์ธ์Šคํ„ด์‹ฑ (Instancing)๊ณผ LOD์˜ ๊ฒฐํ•ฉ

๊ฐ™์€ ๋ชจ๋ธ์ด ์—ฌ๋Ÿฌ ๋ฒˆ ๋ฐ˜๋ณต๋˜๋Š” ๊ฒฝ์šฐ(์˜ˆ: ์ˆฒ์˜ ๋‚˜๋ฌด๋“ค), ์ธ์Šคํ„ด์‹ฑ ๊ธฐ์ˆ ์„ LOD์™€ ๊ฒฐํ•ฉํ•˜๋ฉด ์—„์ฒญ๋‚œ ์„ฑ๋Šฅ ํ–ฅ์ƒ์„ ์–ป์„ ์ˆ˜ ์žˆ์–ด.


// ์ธ์Šคํ„ด์‹ฑ๊ณผ LOD๋ฅผ ๊ฒฐํ•ฉํ•œ ์˜ˆ์‹œ
UFUNCTION(BlueprintCallable, Category="LOD")
void SetupInstancedLOD(UHierarchicalInstancedStaticMeshComponent* HISM)
{
    // LOD ์„ค์ •
    HISM->NumCustomLODs = 3;
    HISM->CustomLODDistances.Add(1000.0f);  // LOD 1 ์ „ํ™˜ ๊ฑฐ๋ฆฌ
    HISM->CustomLODDistances.Add(2000.0f);  // LOD 2 ์ „ํ™˜ ๊ฑฐ๋ฆฌ
    
    // ๊ฐ LOD ๋ ˆ๋ฒจ์— ๋Œ€ํ•œ ๋ฉ”์‹œ ์„ค์ •
    HISM->SetStaticMesh(HighDetailMesh, 0);
    HISM->SetStaticMesh(MediumDetailMesh, 1);
    HISM->SetStaticMesh(LowDetailMesh, 2);
}

์ด ์ฝ”๋“œ๋Š” ๊ณ„์ธต์  ์ธ์Šคํ„ด์Šค ์Šคํƒœํ‹ฑ ๋ฉ”์‹œ ์ปดํฌ๋„ŒํŠธ(HISM)๋ฅผ ์‚ฌ์šฉํ•ด ์—ฌ๋Ÿฌ LOD ๋ ˆ๋ฒจ์„ ์„ค์ •ํ•˜๊ณ  ์žˆ์–ด. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ๋งŽ์€ ์ˆ˜์˜ ๊ฐ™์€ ์˜ค๋ธŒ์ ํŠธ๋ฅผ ํšจ์œจ์ ์œผ๋กœ ๋ Œ๋”๋งํ•˜๋ฉด์„œ๋„ ๊ฑฐ๋ฆฌ์— ๋”ฐ๋ผ ์ ์ ˆํ•œ ๋””ํ…Œ์ผ์„ ์œ ์ง€ํ•  ์ˆ˜ ์žˆ์ง€.

์ธ์Šคํ„ด์‹ฑ๊ณผ LOD ๊ฒฐํ•ฉ ๊ฐœ๋…๋„ LOD 0 LOD 1 LOD 2 LOD 3 LOD 4 ๊ฑฐ๋ฆฌ์— ๋”ฐ๋ฅธ LOD ๋ณ€ํ™” (์ธ์Šคํ„ด์‹ฑ ์ ์šฉ)

์ด๋Ÿฐ ๊ณ ๊ธ‰ ๊ธฐ์ˆ ๋“ค์„ ์ž˜ ํ™œ์šฉํ•˜๋ฉด ๊ฒŒ์ž„์˜ ์„ฑ๋Šฅ๊ณผ ๊ทธ๋ž˜ํ”ฝ ํ€„๋ฆฌํ‹ฐ๋ฅผ ๋™์‹œ์— ๋Œ์–ด์˜ฌ๋ฆด ์ˆ˜ ์žˆ์–ด. ํ•˜์ง€๋งŒ ์ด๋Ÿฐ ๊ธฐ์ˆ ๋“ค์„ ์™„๋ฒฝํ•˜๊ฒŒ ๊ตฌํ˜„ํ•˜๋Š” ๊ฑด ์‰ฝ์ง€ ์•Š์ง€. ๊ทธ๋Ÿด ๋•Œ ์žฌ๋Šฅ๋„ท์—์„œ ์–ธ๋ฆฌ์–ผ ์—”์ง„ ์ „๋ฌธ๊ฐ€๋ฅผ ์ฐพ์•„ ๋„์›€์„ ๋ฐ›๋Š” ๊ฒƒ๋„ ์ข‹์€ ๋ฐฉ๋ฒ•์ด์•ผ. ๊ทธ๋“ค์˜ ๊ฒฝํ—˜๊ณผ ๋…ธํ•˜์šฐ๋กœ ๋” ํšจ์œจ์ ์ด๊ณ  ์ตœ์ ํ™”๋œ LOD ์‹œ์Šคํ…œ์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ์„ ๊ฑฐ์•ผ! ๐ŸŒŸ

๐Ÿงช LOD ์‹œ์Šคํ…œ ํ…Œ์ŠคํŠธ์™€ ๋””๋ฒ„๊น…

LOD ์‹œ์Šคํ…œ์„ ๊ตฌํ˜„ํ–ˆ๋‹ค๋ฉด ์ด์ œ ํ…Œ์ŠคํŠธ์™€ ๋””๋ฒ„๊น…์„ ํ•ด์•ผ ํ•ด. ์ด ๊ณผ์ •์€ ์ •๋ง ์ค‘์š”ํ•˜๋‹ˆ๊นŒ ์ง‘์ค‘ํ•ด์„œ ๋“ค์–ด๋ด! ๐Ÿ˜‰

1. ์‹œ๊ฐ์  ๋””๋ฒ„๊น…

์–ธ๋ฆฌ์–ผ ์—”์ง„์€ LOD ๋ ˆ๋ฒจ์„ ์‹œ๊ฐ์ ์œผ๋กœ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋Š” ํˆด์„ ์ œ๊ณตํ•ด. ์ด๊ฑธ ์‚ฌ์šฉํ•˜๋ฉด ๊ฐ ์˜ค๋ธŒ์ ํŠธ์˜ ํ˜„์žฌ LOD ์ƒํƒœ๋ฅผ ํ•œ๋ˆˆ์— ๋ณผ ์ˆ˜ ์žˆ์ง€.


// ์ฝ˜์†” ๋ช…๋ น์–ด๋กœ LOD ์‹œ๊ฐํ™” ํ™œ์„ฑํ™”
r.ForceLOD 0

์ด ๋ช…๋ น์–ด๋ฅผ ์‹คํ–‰ํ•˜๋ฉด ๊ฐ LOD ๋ ˆ๋ฒจ์ด ๋‹ค๋ฅธ ์ƒ‰์ƒ์œผ๋กœ ํ‘œ์‹œ๋ผ. ๋นจ๊ฐ„์ƒ‰์€ LOD0, ์ดˆ๋ก์ƒ‰์€ LOD1, ํŒŒ๋ž€์ƒ‰์€ LOD2... ์ด๋Ÿฐ ์‹์ด์•ผ.

LOD ์‹œ๊ฐํ™” ์˜ˆ์‹œ LOD 0 LOD 1 LOD 2

2. ์„ฑ๋Šฅ ํ”„๋กœํŒŒ์ผ๋ง

LOD ์‹œ์Šคํ…œ์˜ ํšจ๊ณผ๋ฅผ ์ •ํ™•ํžˆ ์ธก์ •ํ•˜๋ ค๋ฉด ์„ฑ๋Šฅ ํ”„๋กœํŒŒ์ผ๋ง์ด ํ•„์š”ํ•ด. ์–ธ๋ฆฌ์–ผ ์—”์ง„์˜ ๋‚ด์žฅ ํ”„๋กœํŒŒ์ผ๋Ÿฌ๋ฅผ ์‚ฌ์šฉํ•ด๋ณด์ž.


// ์ฝ˜์†” ๋ช…๋ น์–ด๋กœ ํ”„๋กœํŒŒ์ผ๋Ÿฌ ํ™œ์„ฑํ™”
stat unit

์ด ๋ช…๋ น์–ด๋ฅผ ์‹คํ–‰ํ•˜๋ฉด ํ™”๋ฉด์— FPS, ํ”„๋ ˆ์ž„ ์‹œ๊ฐ„, GPU ์‹œ๊ฐ„ ๋“ฑ ๋‹ค์–‘ํ•œ ์„ฑ๋Šฅ ์ง€ํ‘œ๊ฐ€ ํ‘œ์‹œ๋ผ. LOD ์‹œ์Šคํ…œ์„ ์ ์šฉํ•˜๊ธฐ ์ „๊ณผ ํ›„์˜ ์„ฑ๋Šฅ์„ ๋น„๊ตํ•ด๋ณด๋ฉด ๊ทธ ํšจ๊ณผ๋ฅผ ์ •ํ™•ํžˆ ์•Œ ์ˆ˜ ์žˆ์ง€.

๐Ÿ’ก ํ”„๋กœ ํŒ: ์„ฑ๋Šฅ ํ…Œ์ŠคํŠธ๋ฅผ ํ•  ๋•Œ๋Š” ๋‹ค์–‘ํ•œ ํ•˜๋“œ์›จ์–ด ํ™˜๊ฒฝ์—์„œ ํ…Œ์ŠคํŠธํ•ด๋ด์•ผ ํ•ด. ๊ณ ์‚ฌ์–‘ PC์—์„œ๋Š” ๋ฌธ์ œ์—†์ด ๋Œ์•„๊ฐ€๋„ ์ €์‚ฌ์–‘ ๊ธฐ๊ธฐ์—์„œ๋Š” ๋ฌธ์ œ๊ฐ€ ์ƒ๊ธธ ์ˆ˜ ์žˆ๊ฑฐ๋“ . ์žฌ๋Šฅ๋„ท์—์„œ QA ํ…Œ์Šคํ„ฐ๋ฅผ ๊ณ ์šฉํ•ด ๋‹ค์–‘ํ•œ ํ™˜๊ฒฝ์—์„œ ํ…Œ์ŠคํŠธ๋ฅผ ํ•ด๋ณด๋Š” ๊ฒƒ๋„ ์ข‹์€ ๋ฐฉ๋ฒ•์ด์•ผ!

3. ์ž๋™ํ™”๋œ LOD ํ…Œ์ŠคํŠธ

์ˆ˜๋™์œผ๋กœ ํ…Œ์ŠคํŠธํ•˜๋Š” ๊ฒƒ๋„ ์ข‹์ง€๋งŒ, ์ž๋™ํ™”๋œ ํ…Œ์ŠคํŠธ๋ฅผ ๋งŒ๋“ค๋ฉด ๋” ํšจ์œจ์ ์ด์•ผ. ์–ธ๋ฆฌ์–ผ์˜ Automation System์„ ์‚ฌ์šฉํ•ด์„œ LOD ํ…Œ์ŠคํŠธ๋ฅผ ์ž๋™ํ™”ํ•  ์ˆ˜ ์žˆ์–ด.


// ์ž๋™ํ™”๋œ LOD ํ…Œ์ŠคํŠธ ์˜ˆ์‹œ
IMPLEMENT_SIMPLE_AUTOMATION_TEST(FLODTest, "LOD.BasicTest", EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter)

bool FLODTest::RunTest(const FString& Parameters)
{
    UWorld* World = GEngine->GetWorldContexts()[0].World();
    APlayerController* PlayerController = World->GetFirstPlayerController();
    
    // ํ…Œ์ŠคํŠธํ•  ์•กํ„ฐ ์Šคํฐ
    AActor* TestActor = World->SpawnActor<aactor>(TestActorClass);
    
    // ์นด๋ฉ”๋ผ ์œ„์น˜ ๋ณ€๊ฒฝํ•˜๋ฉฐ LOD ๋ ˆ๋ฒจ ์ฒดํฌ
    for (float Distance = 100.0f; Distance <= 1000.0f; Distance += 100.0f)
    {
        PlayerController->SetViewTarget(TestActor);
        PlayerController->GetPawn()->SetActorLocation(FVector(Distance, 0, 0));
        
        int32 CurrentLOD = TestActor->GetLODLevel();
        int32 ExpectedLOD = GetExpectedLODForDistance(Distance);
        
        TestEqual(FString::Printf(TEXT("LOD level at distance %f"), Distance), CurrentLOD, ExpectedLOD);
    }
    
    return true;
}
</aactor>

์ด ํ…Œ์ŠคํŠธ๋Š” ๋‹ค์–‘ํ•œ ๊ฑฐ๋ฆฌ์—์„œ ์•กํ„ฐ์˜ LOD ๋ ˆ๋ฒจ์„ ์ฒดํฌํ•ด. ์˜ˆ์ƒํ•œ LOD ๋ ˆ๋ฒจ๊ณผ ์‹ค์ œ LOD ๋ ˆ๋ฒจ์ด ์ผ์น˜ํ•˜๋Š”์ง€ ํ™•์ธํ•˜๋Š” ๊ฑฐ์ง€.

4. LOD ์ „ํ™˜ ๋ถ€๋“œ๋Ÿฌ์›€ ํ…Œ์ŠคํŠธ

LOD ๋ ˆ๋ฒจ์ด ๋ฐ”๋€” ๋•Œ ๋ถ€์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ "ํŒํ•‘"๋˜๋Š” ํ˜„์ƒ์ด ์—†๋Š”์ง€ ํ™•์ธํ•ด์•ผ ํ•ด. ์ด๋ฅผ ์œ„ํ•ด ์นด๋ฉ”๋ผ๋ฅผ ์ฒœ์ฒœํžˆ ์›€์ง์ด๋ฉด์„œ LOD ์ „ํ™˜์ด ๋ถ€๋“œ๋Ÿฝ๊ฒŒ ์ผ์–ด๋‚˜๋Š”์ง€ ๊ด€์ฐฐํ•ด๋ด.


// LOD ์ „ํ™˜ ํ…Œ์ŠคํŠธ๋ฅผ ์œ„ํ•œ ์นด๋ฉ”๋ผ ์ด๋™ ํ•จ์ˆ˜
UFUNCTION(BlueprintCallable, Category="LOD Test")
void MoveCameraForLODTest()
{
    APlayerController* PlayerController = GetWorld()->GetFirstPlayerController();
    FVector StartLocation = PlayerController->GetPawn()->GetActorLocation();
    FVector EndLocation = StartLocation + FVector(1000, 0, 0);
    
    float ElapsedTime = 0.0f;
    float Duration = 10.0f;  // 10์ดˆ ๋™์•ˆ ์ด๋™
    
    while (ElapsedTime < Duration)
    {
        float Alpha = ElapsedTime / Duration;
        FVector NewLocation = FMath::Lerp(StartLocation, EndLocation, Alpha);
        PlayerController->GetPawn()->SetActorLocation(NewLocation);
        
        ElapsedTime += GetWorld()->GetDeltaSeconds();
        yield return null;  // ํ”„๋ ˆ์ž„๋งˆ๋‹ค ์‹คํ–‰
    }
}

์ด ํ•จ์ˆ˜๋ฅผ ์‹คํ–‰ํ•˜๋ฉด ์นด๋ฉ”๋ผ๊ฐ€ ์ฒœ์ฒœํžˆ ์ด๋™ํ•˜๋ฉด์„œ LOD ์ „ํ™˜์„ ๊ด€์ฐฐํ•  ์ˆ˜ ์žˆ์–ด. ์ „ํ™˜์ด ๋„ˆ๋ฌด ๊ฐ‘์ž‘์Šค๋Ÿฝ๊ฒŒ ์ผ์–ด๋‚œ๋‹ค๋ฉด LOD ์„ค์ •์„ ์กฐ์ •ํ•ด์•ผ ํ•  ๊ฑฐ์•ผ.

LOD ์ „ํ™˜ ๋ถ€๋“œ๋Ÿฌ์›€ ํ…Œ์ŠคํŠธ ์นด๋ฉ”๋ผ ์ด๋™ ๋ฐฉํ–ฅ

์ด๋Ÿฐ ํ…Œ์ŠคํŠธ์™€ ๋””๋ฒ„๊น… ๊ณผ์ •์„ ๊ฑฐ์น˜๋ฉด LOD ์‹œ์Šคํ…œ์˜ ์„ฑ๋Šฅ๊ณผ ์‹œ๊ฐ์  ํ’ˆ์งˆ์„ ํฌ๊ฒŒ ํ–ฅ์ƒ์‹œํ‚ฌ ์ˆ˜ ์žˆ์–ด. ํ•˜์ง€๋งŒ ์ด ๊ณผ์ •์ด ๋ณต์žกํ•˜๊ณ  ์‹œ๊ฐ„์ด ๋งŽ์ด ๊ฑธ๋ฆฐ๋‹ค๊ณ  ๋Š๋‚€๋‹ค๋ฉด, ์žฌ๋Šฅ๋„ท์—์„œ ๊ฒŒ์ž„ ์ตœ์ ํ™” ์ „๋ฌธ๊ฐ€๋ฅผ ์ฐพ์•„๋ณด๋Š” ๊ฒƒ๋„ ์ข‹์€ ๋ฐฉ๋ฒ•์ด์•ผ. ๊ทธ๋“ค์˜ ๊ฒฝํ—˜๊ณผ ์ „๋ฌธ ์ง€์‹์œผ๋กœ ๋” ํšจ์œจ์ ์ธ ํ…Œ์ŠคํŠธ์™€ ๋””๋ฒ„๊น…์„ ํ•  ์ˆ˜ ์žˆ์„ ๊ฑฐ์•ผ! ๐Ÿš€

๐ŸŽฎ ์‹ค์ œ ๊ฒŒ์ž„์—์„œ์˜ LOD ์‹œ์Šคํ…œ ํ™œ์šฉ ์‚ฌ๋ก€

์ž, ์ด์ œ ์šฐ๋ฆฌ๊ฐ€ ๋ฐฐ์šด LOD ์‹œ์Šคํ…œ์ด ์‹ค์ œ ๊ฒŒ์ž„์—์„œ ์–ด๋–ป๊ฒŒ ํ™œ์šฉ๋˜๋Š”์ง€ ๋ช‡ ๊ฐ€์ง€ ์‚ฌ๋ก€๋ฅผ ํ†ตํ•ด ์•Œ์•„๋ณด์ž. ์ด ์‚ฌ๋ก€๋“ค์„ ํ†ตํ•ด LOD ์‹œ์Šคํ…œ์˜ ์‹ค์ œ ํšจ๊ณผ์™€ ์ค‘์š”์„ฑ์„ ๋” ์ž˜ ์ดํ•ดํ•  ์ˆ˜ ์žˆ์„ ๊ฑฐ์•ผ.

1. ์˜คํ”ˆ ์›”๋“œ ๊ฒŒ์ž„์˜ ์ง€ํ˜•

์˜คํ”ˆ ์›”๋“œ ๊ฒŒ์ž„์—์„œ ๊ด‘ํ™œํ•œ ์ง€ํ˜•์„ ํšจ์œจ์ ์œผ๋กœ ๋ Œ๋”๋งํ•˜๋Š” ๊ฒƒ์€ ๋งค์šฐ ์ค‘์š”ํ•ด. LOD ์‹œ์Šคํ…œ์„ ํ™œ์šฉํ•˜๋ฉด ์ด๋ฅผ ํšจ๊ณผ์ ์œผ๋กœ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ์ง€.


// ์ง€ํ˜• LOD ์„ค์ • ์˜ˆ์‹œ
UFUNCTION(BlueprintCallable, Category="Terrain LOD")
void SetupTerrainLOD(ULandscapeComponent* LandscapeComponent)
{
    // LOD ๊ฑฐ๋ฆฌ ์„ค์ •
    LandscapeComponent->LODDistanceFactor = 2.0f;
    
    // LOD ๋ ˆ๋ฒจ ์„ค์ •
    LandscapeComponent->ForcedLOD = 0;  // ๊ฐ€์žฅ ๊ฐ€๊นŒ์šด ๊ฑฐ๋ฆฌ์—์„œ๋Š” ์ตœ๊ณ  ํ’ˆ์งˆ
    LandscapeComponent->LOD0DistributionSetting = 1.0f;
    LandscapeComponent->LOD0ScreenSize = 0.5f;
    
    // ๋ฉ€๋ฆฌ ์žˆ๋Š” ์ง€ํ˜•์— ๋Œ€ํ•œ LOD ์„ค์ •
    LandscapeComponent->LODFalloff = ELandscapeLODFalloff::Linear;
}

์ด๋Ÿฐ ์‹์œผ๋กœ ์ง€ํ˜•์˜ LOD๋ฅผ ์„ค์ •ํ•˜๋ฉด, ํ”Œ๋ ˆ์ด์–ด ์ฃผ๋ณ€์˜ ์ง€ํ˜•์€ ์ƒ์„ธํ•˜๊ฒŒ ํ‘œํ˜„๋˜๊ณ  ๋ฉ€๋ฆฌ ์žˆ๋Š” ์ง€ํ˜•์€ ๋‹จ์ˆœํ™”๋ผ์„œ ๋ Œ๋”๋ง๋ผ. ์ด๋ฅผ ํ†ตํ•ด ๋„“์€ ์ง€ํ˜•์„ ํšจ์œจ์ ์œผ๋กœ ํ‘œํ˜„ํ•  ์ˆ˜ ์žˆ์ง€.

2. ๋„์‹œ ์‹œ๋ฎฌ๋ ˆ์ด์…˜ ๊ฒŒ์ž„์˜ ๊ฑด๋ฌผ

๋„์‹œ ์‹œ๋ฎฌ๋ ˆ์ด์…˜ ๊ฒŒ์ž„์—์„œ๋Š” ์ˆ˜๋งŽ์€ ๊ฑด๋ฌผ์„ ๋™์‹œ์— ๋ Œ๋”๋งํ•ด์•ผ ํ•ด. ์ด๋•Œ LOD ์‹œ์Šคํ…œ์„ ํ™œ์šฉํ•˜๋ฉด ์„ฑ๋Šฅ์„ ํฌ๊ฒŒ ํ–ฅ์ƒ์‹œํ‚ฌ ์ˆ˜ ์žˆ์–ด.


// ๊ฑด๋ฌผ LOD ์‹œ์Šคํ…œ ๊ตฌํ˜„ ์˜ˆ์‹œ
UFUNCTION(BlueprintCallable, Category="Building LOD")
void SetupBuildingLOD(UStaticMeshComponent* BuildingMesh)
{
    // LOD ์„ค์ •
    BuildingMesh->SetLODDataCount(3, BuildingMesh->LODData.Num());
    
    // ๊ฐ LOD ๋ ˆ๋ฒจ์— ๋Œ€ํ•œ ๊ฑฐ๋ฆฌ ์„ค์ •
    BuildingMesh->LODData[0].ScreenSize = 1.0f;
    BuildingMesh->LODData[1].ScreenSize = 0.5f;
    BuildingMesh->LODData[2].ScreenSize = 0.1f;
    
    // LOD ๋ ˆ๋ฒจ์— ๋”ฐ๋ฅธ ๋ฉ”์‹œ ์„ค์ •
    BuildingMesh->SetStaticMesh(HighDetailBuildingMesh, 0);
    BuildingMesh->SetStaticMesh(MediumDetailBuildingMesh, 1);
    BuildingMesh->SetStaticMesh(LowDetailBuildingMesh, 2);
}

์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ๊ฐ€๊นŒ์ด ์žˆ๋Š” ๊ฑด๋ฌผ์€ ์ƒ์„ธํ•˜๊ฒŒ, ๋ฉ€๋ฆฌ ์žˆ๋Š” ๊ฑด๋ฌผ์€ ๋‹จ์ˆœํ™”๋œ ๋ชจ๋ธ๋กœ ํ‘œํ˜„๋ผ. ์ˆ˜์ฒœ ๊ฐœ์˜ ๊ฑด๋ฌผ์ด ์žˆ๋Š” ๋„์‹œ๋„ ๋ถ€๋“œ๋Ÿฝ๊ฒŒ ๋ Œ๋”๋งํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋˜๋Š” ๊ฑฐ์ง€.

๋„์‹œ ์‹œ๋ฎฌ๋ ˆ์ด์…˜ ๊ฒŒ์ž„์˜ LOD ์‹œ์Šคํ…œ LOD์— ๋”ฐ๋ฅธ ๊ฑด๋ฌผ ์ƒ์„ธ๋„ ๋ณ€ํ™”

3. ๋ ˆ์ด์‹ฑ ๊ฒŒ์ž„์˜ ์ฐจ๋Ÿ‰

๋ ˆ์ด์‹ฑ ๊ฒŒ์ž„์—์„œ๋Š” ๋น ๋ฅด๊ฒŒ ์›€์ง์ด๋Š” ์ฐจ๋Ÿ‰์˜ ๋””ํ…Œ์ผ์„ ํšจ์œจ์ ์œผ๋กœ ๊ด€๋ฆฌํ•ด์•ผ ํ•ด. LOD ์‹œ์Šคํ…œ์„ ํ™œ์šฉํ•˜๋ฉด ์ด๋ฅผ ํšจ๊ณผ์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์ง€.


// ์ฐจ๋Ÿ‰ LOD ์‹œ์Šคํ…œ ๊ตฌํ˜„ ์˜ˆ์‹œ
UFUNCTION(BlueprintCallable, Category="Vehicle LOD")
void SetupVehicleLOD(USkeletalMeshComponent* VehicleMesh)
{
    // LOD ์„ค์ •
    VehicleMesh->SetLODDataCount(4, VehicleMesh->LODData.Num());
    
    // ๊ฐ LOD ๋ ˆ๋ฒจ์— ๋Œ€ํ•œ ๊ฑฐ๋ฆฌ ์„ค์ •
    VehicleMesh->LODData[0].ScreenSize = 1.0f;
    VehicleMesh->LODData[1].ScreenSize = 0.6f;
    VehicleMesh->LODData[2].ScreenSize = 0.3f;
    VehicleMesh->LODData[3].ScreenSize = 0.1f;
    
    // LOD ๋ ˆ๋ฒจ์— ๋”ฐ๋ฅธ ๋ฉ”์‹œ ๋ฐ ๋จธํ‹ฐ๋ฆฌ์–ผ ์„ค์ •
    VehicleMesh->SetSkeletalMesh(HighDetailVehicleMesh, 0);
    VehicleMesh->SetSkeletalMesh(MediumDetailVehicleMesh, 1);
    VehicleMesh->SetSkeletalMesh(LowDetailVehicleMesh, 2);
    VehicleMesh->SetSkeletalMesh(VeryLowDetailVehicleMesh, 3);
    
    // ๋™์  ๋จธํ‹ฐ๋ฆฌ์–ผ ์ธ์Šคํ„ด์Šค ์ƒ์„ฑ ๋ฐ ์„ค์ •
    UMaterialInstanceDynamic* DynMaterial = UMaterialInstanceDynamic::Create(VehicleBaseMaterial, this);
    VehicleMesh->SetMaterial(0, DynMaterial);
    
    // LOD์— ๋”ฐ๋ฅธ ๋จธํ‹ฐ๋ฆฌ์–ผ ํŒŒ๋ผ๋ฏธํ„ฐ ์กฐ์ •
    DynMaterial->SetScalarParameterValue("DetailLevel", 1.0f);  // ์ตœ๊ณ  ํ’ˆ์งˆ
}

์ด๋Ÿฐ ์‹์œผ๋กœ LOD๋ฅผ ์„ค์ •ํ•˜๋ฉด, ๊ฐ€๊นŒ์ด ์žˆ๋Š” ์ฐจ๋Ÿ‰์€ ๊ณ ํ’ˆ์งˆ๋กœ ๋ Œ๋”๋ง๋˜๊ณ  ๋ฉ€๋ฆฌ ์žˆ๋Š” ์ฐจ๋Ÿ‰์€ ๋‹จ์ˆœํ™”๋œ ๋ชจ๋ธ๊ณผ ํ…์Šค์ฒ˜๋กœ ํ‘œํ˜„๋ผ. ๋น ๋ฅด๊ฒŒ ์›€์ง์ด๋Š” ๋ ˆ์ด์‹ฑ ๊ฒŒ์ž„์—์„œ๋„ ๋ถ€๋“œ๋Ÿฌ์šด ๊ทธ๋ž˜ํ”ฝ์„ ์œ ์ง€ํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋˜๋Š” ๊ฑฐ์•ผ.

๐ŸŽ๏ธ ๋ ˆ์ด์‹ฑ ๊ฒŒ์ž„ ํŒ: ๋ ˆ์ด์‹ฑ ๊ฒŒ์ž„์—์„œ๋Š” ์†๋„๊ฐ๋„ ์ค‘์š”ํ•ด. LOD ์‹œ์Šคํ…œ์„ ๊ตฌํ˜„ํ•  ๋•Œ ์ฐจ๋Ÿ‰์˜ ์†๋„์— ๋”ฐ๋ผ LOD ์ „ํ™˜ ๊ฑฐ๋ฆฌ๋ฅผ ๋™์ ์œผ๋กœ ์กฐ์ ˆํ•˜๋ฉด ๋” ๋‚˜์€ ์‹œ๊ฐ์  ํšจ๊ณผ๋ฅผ ์–ป์„ ์ˆ˜ ์žˆ์–ด. ๋น ๋ฅด๊ฒŒ ์›€์ง์ผ ๋•Œ๋Š” LOD ์ „ํ™˜ ๊ฑฐ๋ฆฌ๋ฅผ ์ข€ ๋” ๋ฉ€๋ฆฌ ์„ค์ •ํ•˜๋Š” ๊ฑฐ์ง€.

์ด๋Ÿฐ ์‹ค์ œ ์‚ฌ๋ก€๋“ค์„ ๋ณด๋ฉด LOD ์‹œ์Šคํ…œ์ด ์–ผ๋งˆ๋‚˜ ๊ฐ•๋ ฅํ•˜๊ณ  ์œ ์šฉํ•œ์ง€ ์•Œ ์ˆ˜ ์žˆ์ง€? ํ•˜์ง€๋งŒ ๊ฐ ๊ฒŒ์ž„์˜ ํŠน์„ฑ์— ๋งž๊ฒŒ LOD ์‹œ์Šคํ…œ์„ ์ตœ์ ํ™”ํ•˜๋Š” ๊ฑด ์‰ฝ์ง€ ์•Š์€ ์ž‘์—…์ด์•ผ. ์ด๋Ÿด ๋•Œ ์žฌ๋Šฅ๋„ท์—์„œ ํ•ด๋‹น ์žฅ๋ฅด์˜ ๊ฒŒ์ž„ ์ตœ์ ํ™” ์ „๋ฌธ๊ฐ€๋ฅผ ์ฐพ์•„ ์กฐ์–ธ์„ ๊ตฌํ•˜๋ฉด ํฐ ๋„์›€์ด ๋  ๊ฑฐ์•ผ. ๊ทธ๋“ค์˜ ๊ฒฝํ—˜๊ณผ ๋…ธํ•˜์šฐ๋กœ ๋„ค ๊ฒŒ์ž„์— ๋”ฑ ๋งž๋Š” LOD ์‹œ์Šคํ…œ์„ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์„ ๊ฑฐ์•ผ! ๐ŸŽฎโœจ

๐Ÿš€ LOD ์‹œ์Šคํ…œ์˜ ๋ฏธ๋ž˜์™€ ๋ฐœ์ „ ๋ฐฉํ–ฅ

์ž, ์ด์ œ ์šฐ๋ฆฌ๊ฐ€ ํ˜„์žฌ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋Š” LOD ์‹œ์Šคํ…œ์— ๋Œ€ํ•ด ๋งŽ์ด ์•Œ๊ฒŒ ๋์–ด. ๊ทธ๋Ÿผ ์ด์ œ ๋ฏธ๋ž˜๋ฅผ ํ•œ๋ฒˆ ๋‚ด๋‹ค๋ณผ๊นŒ? LOD ๊ธฐ์ˆ ์€ ๊ณ„์†ํ•ด์„œ ๋ฐœ์ „ํ•˜๊ณ  ์žˆ๊ณ , ์•ž์œผ๋กœ ๋” ํฅ๋ฏธ์ง„์ง„ํ•œ ๋ณ€ํ™”๊ฐ€ ์žˆ์„ ๊ฑฐ์•ผ. ๊ทธ ์ค‘ ๋ช‡ ๊ฐ€์ง€๋ฅผ ์‚ดํŽด๋ณด์ž!

1. AI ๊ธฐ๋ฐ˜ ๋™์  LOD ์‹œ์Šคํ…œ

์ธ๊ณต์ง€๋Šฅ ๊ธฐ์ˆ ์ด ๋ฐœ์ „ํ•˜๋ฉด์„œ, AI๋ฅผ ํ™œ์šฉํ•œ ๋™์  LOD ์‹œ์Šคํ…œ์ด ๋“ฑ์žฅํ•  ๊ฑฐ์•ผ. ์ด ์‹œ์Šคํ…œ์€ ํ”Œ๋ ˆ์ด์–ด์˜ ํ–‰๋™ ํŒจํ„ด, ์‹œ์„  ๋ฐฉํ–ฅ, ๊ฒŒ์ž„ ์ƒํ™ฉ ๋“ฑ์„ ์‹ค์‹œ๊ฐ„์œผ๋กœ ๋ถ„์„ํ•ด์„œ ๊ฐ€์žฅ ํšจ์œจ์ ์ธ LOD ์„ค์ •์„ ์ž๋™์œผ๋กœ ๊ฒฐ์ •ํ•ด.


// AI ๊ธฐ๋ฐ˜ ๋™์  LOD ์‹œ์Šคํ…œ ์˜ˆ์‹œ (๋ฏธ๋ž˜์˜ ๊ฐ€์ƒ ์ฝ”๋“œ)
class AILODSystem : public USubsystem
{
public:
    void UpdateLOD()
    {
        // ํ”Œ๋ ˆ์ด์–ด ํ–‰๋™ ๋ถ„์„
        FPlayerBehavior Behavior = AnalyzePlayerBehavior();
        
        // ๊ฒŒ์ž„ ์ƒํ™ฉ ๋ถ„์„
        FGameSituation Situation = AnalyzeGameSituation();
        
        // AI ๋ชจ๋ธ์„ ํ†ตํ•œ ์ตœ์  LOD ์„ค์ • ์˜ˆ์ธก
        FLODSettings OptimalSettings = AIModel->PredictOptimalLOD(Behavior, Situation);
        
        // ์˜ˆ์ธก๋œ ์„ค์ • ์ ์šฉ
        ApplyLODSettings(OptimalSettings);
    }
};

์ด๋Ÿฐ ์‹œ์Šคํ…œ์ด ์‹คํ˜„๋˜๋ฉด, ๊ฒŒ์ž„์€ ํ”Œ๋ ˆ์ด์–ด์˜ ์ƒํ™ฉ์— ๋”์šฑ ๋งž์ถคํ™”๋œ ๊ทธ๋ž˜ํ”ฝ์„ ์ œ๊ณตํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋  ๊ฑฐ์•ผ. ์˜ˆ๋ฅผ ๋“ค์–ด, ํ”Œ๋ ˆ์ด์–ด๊ฐ€ ์ „ํˆฌ ์ค‘์ผ ๋•Œ๋Š” ์ „ํˆฌ์™€ ๊ด€๋ จ๋œ ์š”์†Œ์— ๋” ๋†’์€ LOD๋ฅผ ์ ์šฉํ•˜๊ณ , ํƒํ—˜ ์ค‘์ผ ๋•Œ๋Š” ๋จผ ๊ฑฐ๋ฆฌ์˜ ํ’๊ฒฝ์— ๋” ๋†’์€ LOD๋ฅผ ์ ์šฉํ•˜๋Š” ์‹์ด์ง€.

2. ์‹ค์‹œ๊ฐ„ ์ง€์˜ค๋ฉ”ํŠธ๋ฆฌ ์ƒ์„ฑ

๋ฏธ๋ฆฌ ๋งŒ๋“ค์–ด์ง„ LOD ๋ชจ๋ธ์„ ์‚ฌ์šฉํ•˜๋Š” ๋Œ€์‹ , ์‹ค์‹œ๊ฐ„์œผ๋กœ ์ง€์˜ค๋ฉ”ํŠธ๋ฆฌ๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ์กฐ์ •ํ•˜๋Š” ๊ธฐ์ˆ ์ด ๋ฐœ์ „ํ•  ๊ฑฐ์•ผ. ์ด ๊ธฐ์ˆ ์„ ์‚ฌ์šฉํ•˜๋ฉด ๋” ์œ ์—ฐํ•˜๊ณ  ์ž์—ฐ์Šค๋Ÿฌ์šด LOD ์ „ํ™˜์ด ๊ฐ€๋Šฅํ•ด์ ธ.


// ์‹ค์‹œ๊ฐ„ ์ง€์˜ค๋ฉ”ํŠธ๋ฆฌ ์ƒ์„ฑ ์˜ˆ์‹œ (๋ฏธ๋ž˜์˜ ๊ฐ€์ƒ ์ฝ”๋“œ)
class RealTimeGeometryLOD : public UActorComponent
{
public:
    void UpdateGeometry()
    {
        float DistanceToCamera = GetDistanceToCamera();
        float DetailLevel = CalculateDetailLevel(DistanceToCamera);
        
        // ์‹ค์‹œ๊ฐ„์œผ๋กœ ์ง€์˜ค๋ฉ”ํŠธ๋ฆฌ ์ƒ์„ฑ ๋ฐ ์กฐ์ •
        FMeshDescription NewMesh = GenerateMesh(DetailLevel);
        UpdateRenderingMesh(NewMesh);
    }
};

์ด ๊ธฐ์ˆ ์ด ์‹คํ˜„๋˜๋ฉด, ์˜ค๋ธŒ์ ํŠธ์˜ ํ˜•ํƒœ์™€ ๋ณต์žก๋„๋ฅผ ์ƒํ™ฉ์— ๋”ฐ๋ผ ์‹ค์‹œ๊ฐ„์œผ๋กœ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋ผ. ์˜ˆ๋ฅผ ๋“ค์–ด, ํญ๋ฐœ๋กœ ์ธํ•ด ๊ฑด๋ฌผ์ด ๋ถ€์„œ์งˆ ๋•Œ, ๋ถ€์„œ์ง„ ํ˜•ํƒœ์— ๋งž์ถฐ ์‹ค์‹œ๊ฐ„์œผ๋กœ LOD ๋ชจ๋ธ์„ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ๋Š” ๊ฑฐ์ง€.

์‹ค์‹œ๊ฐ„ ์ง€์˜ค๋ฉ”ํŠธ๋ฆฌ ์ƒ์„ฑ ๊ฐœ๋…๋„ ์›๋ณธ ๋ชจ๋ธ ์‹ค์‹œ๊ฐ„ ์ƒ์„ฑ ๋ชจ๋ธ ๋‹จ์ˆœํ™”๋œ ๋ชจ๋ธ

3. ์‹ ๊ฒฝ๋ง ๊ธฐ๋ฐ˜ ํ…์Šค์ฒ˜ ์—…์Šค์ผ€์ผ๋ง

LOD ์‹œ์Šคํ…œ์˜ ๋˜ ๋‹ค๋ฅธ ๋ฐœ์ „ ๋ฐฉํ–ฅ์€ ํ…์Šค์ฒ˜ ์ฒ˜๋ฆฌ์•ผ. ์‹ ๊ฒฝ๋ง์„ ์ด์šฉํ•œ ์‹ค์‹œ๊ฐ„ ํ…์Šค์ฒ˜ ์—…์Šค์ผ€์ผ๋ง ๊ธฐ์ˆ ์ด ๋ฐœ์ „ํ•˜๋ฉด, ์ €ํ•ด์ƒ๋„ ํ…์Šค์ฒ˜๋ฅผ ๊ณ ํ’ˆ์งˆ๋กœ ๋ณ€ํ™˜ํ•ด์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋  ๊ฑฐ์•ผ.


// ์‹ ๊ฒฝ๋ง ๊ธฐ๋ฐ˜ ํ…์Šค์ฒ˜ ์—…์Šค์ผ€์ผ๋ง ์˜ˆ์‹œ (๋ฏธ๋ž˜์˜ ๊ฐ€์ƒ ์ฝ”๋“œ)
class NeuralTextureUpscaler : public USubsystem
{
public:
    UTexture2D* UpscaleTexture(UTexture2D* LowResTexture, int32 TargetResolution)
    {
        // ์ €ํ•ด์ƒ๋„ ํ…์Šค์ฒ˜๋ฅผ ์‹ ๊ฒฝ๋ง ๋ชจ๋ธ์— ์ž…๋ ฅ
        FTextureData HighResData = NeuralNetwork->Upscale(LowResTexture->GetData(), TargetResolution);
        
        // ์—…์Šค์ผ€์ผ๋œ ํ…์Šค์ฒ˜ ์ƒ์„ฑ ๋ฐ ๋ฐ˜ํ™˜
        return CreateTexture(HighResData);
    }
};

์ด ๊ธฐ์ˆ ์ด ์‹คํ˜„๋˜๋ฉด, ๋ฉ€๋ฆฌ ์žˆ๋Š” ์˜ค๋ธŒ์ ํŠธ์— ์ €ํ•ด์ƒ๋„ ํ…์Šค์ฒ˜๋ฅผ ์‚ฌ์šฉํ•˜๋‹ค๊ฐ€ ๊ฐ€๊นŒ์›Œ์ง€๋ฉด ์‹ค์‹œ๊ฐ„์œผ๋กœ ๊ณ ํ•ด์ƒ๋„๋กœ ๋ณ€ํ™˜ํ•ด์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋ผ. ์ด๋ฅผ ํ†ตํ•ด ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰์€ ์ค„์ด๋ฉด์„œ๋„ ์‹œ๊ฐ์  ํ’ˆ์งˆ์€ ์œ ์ง€ํ•  ์ˆ˜ ์žˆ์ง€.

๐Ÿ”ฎ ๋ฏธ๋ž˜ ์ „๋ง: ์ด๋Ÿฐ ์ฒจ๋‹จ ๊ธฐ์ˆ ๋“ค์ด ์‹ค์ œ ๊ฒŒ์ž„์— ์ ์šฉ๋˜๋ ค๋ฉด ์•„์ง ์‹œ๊ฐ„์ด ์ข€ ๊ฑธ๋ฆฌ๊ฒ ์ง€๋งŒ, ๊ธฐ์ˆ ์˜ ๋ฐœ์ „ ์†๋„๋ฅผ ๋ณด๋ฉด ์ƒ๊ฐ๋ณด๋‹ค ๋นจ๋ฆฌ ํ˜„์‹คํ™”๋  ์ˆ˜๋„ ์žˆ์–ด. ์•ž์œผ๋กœ LOD ์‹œ์Šคํ…œ์€ ๋”์šฑ ์ง€๋Šฅ์ ์ด๊ณ  ํšจ์œจ์ ์œผ๋กœ ๋ฐœ์ „ํ•  ๊ฑฐ์•ผ. ๊ทธ๋ฆฌ๊ณ  ์ด๋Ÿฐ ๊ธฐ์ˆ ๋“ค์ด ์ƒ์šฉํ™”๋˜๋ฉด, ์šฐ๋ฆฌ๊ฐ€ ๋งŒ๋“œ๋Š” ๊ฒŒ์ž„์˜ ๊ทธ๋ž˜ํ”ฝ ํ’ˆ์งˆ๊ณผ ์„ฑ๋Šฅ์ด ํฌ๊ฒŒ ํ–ฅ์ƒ๋  ๊ฑฐ๋ผ๊ณ  ํ™•์‹ ํ•ด!

๋ฌผ๋ก  ์ด๋Ÿฐ ๋ฏธ๋ž˜ ๊ธฐ์ˆ ์„ ์ง€๊ธˆ ๋‹น์žฅ ์‚ฌ์šฉํ•  ์ˆœ ์—†๊ฒ ์ง€๋งŒ, ์ด๋Ÿฐ ๋ฐฉํ–ฅ์œผ๋กœ ๊ธฐ์ˆ ์ด ๋ฐœ์ „ํ•˜๊ณ  ์žˆ๋‹ค๋Š” ๊ฑธ ์•Œ์•„๋‘๋ฉด ์ข‹์•„. ๊ทธ๋ฆฌ๊ณ  ์ด๋Ÿฐ ์ฒจ๋‹จ ๊ธฐ์ˆ ์— ๊ด€์‹ฌ์ด ์žˆ๋‹ค๋ฉด, ์žฌ๋Šฅ๋„ท์—์„œ ๊ด€๋ จ ๋ถ„์•ผ์˜ ์ „๋ฌธ๊ฐ€๋“ค๊ณผ ๊ต๋ฅ˜ํ•ด๋ณด๋Š” ๊ฒƒ๋„ ์ข‹์€ ๋ฐฉ๋ฒ•์ด์•ผ. ๊ทธ๋“ค์˜ ์ธ์‚ฌ์ดํŠธ๋ฅผ ํ†ตํ•ด ๋ฏธ๋ž˜์˜ ๊ฒŒ์ž„ ๊ฐœ๋ฐœ ํŠธ๋ Œ๋“œ๋ฅผ ๋ฏธ๋ฆฌ ํŒŒ์•…ํ•˜๊ณ  ์ค€๋น„ํ•  ์ˆ˜ ์žˆ์„ ๊ฑฐ์•ผ! ๐Ÿš€๐ŸŽฎ