diff --git a/Assets/WL/Island/WLIslandSettings.cs b/Assets/WL/Island/WLIslandSettings.cs index 01edda2ab..1bb7d35c7 100644 --- a/Assets/WL/Island/WLIslandSettings.cs +++ b/Assets/WL/Island/WLIslandSettings.cs @@ -351,12 +351,16 @@ namespace WL.Island "๐Ÿ”ด 0 ์ด๋ฉด ๋˜์ „ ์ž…๊ตฌ ๋”๋ฏธ์™€ ๊ฐ™์€ URP/Lit ๋งŒ ์“ด๋‹ค(์•ˆ ๋ณด์ด๋ฉด 0 ์œผ๋กœ ๋˜๋Œ๋ฆด ๊ฒƒ).")] public int platformUseToon = 1; - [Tooltip("์•ˆ๋‚ด ๊ธ€ โ€” {0} = ์ง€๊ธˆ ๋‚ด์•ผ ํ•˜๋Š” ๊ณจ๋“œ.")] - public string platformLabelFormat = "๋˜์ „ ์—ด๊ธฐ\n{0} ๊ณจ๋“œ"; + [Tooltip("์•ˆ๋‚ด ๊ธ€ โ€” {0} = ์ง€๊ธˆ ๋‚ด์•ผ ํ•˜๋Š” ๊ณจ๋“œ. ๐Ÿ”ด 2026-09-16 PD ใ€Œ๋ฐœํŒ ๊ธ€์ž์—์„œ ๋น„์šฉ์€ ๋นผ๋ผ(๋ฐ”๋‹ฅ ๊ฐ€๊ฒฉํŒ์ด ์ด๋ฏธ ๋ณด์—ฌ ์ค€๋‹ค)ใ€ โ†’ ๊ฐ€๊ฒฉ ์—†์ด ์ด๋ฆ„๋งŒ.")] + public string platformLabelFormat = "๋˜์ „ ์—ด๊ธฐ"; [Tooltip("์•ˆ๋‚ด ๊ธ€ ๋†’์ด(m).")] public float platformLabelHeight = 2.2f; + [Tooltip("1 = ๊ธ€์ž๋ฅผ ๋ฐ”๋‹ฅ ํŒ๊ณผ 90ยฐ ๋กœ **์„ธ์›Œ์„œ** ์นด๋ฉ”๋ผ(ํ”Œ๋ ˆ์ด์–ด ์ •๋ฉด) ์ชฝ ๋ฐฉํ–ฅ๋งŒ ๋”ฐ๋ผ ๋ˆ๋‹ค(๊ธฐ์šธ์ด์ง€ ์•Š์Œ ยท 2026-09-16 PD). " + + "0 = ์˜ˆ์ „์ฒ˜๋Ÿผ ์นด๋ฉ”๋ผ๋ฅผ ์ •๋ฉด์œผ๋กœ ๋ฐ”๋ผ๋ณด๊ฒŒ ๊ธฐ์šธ์ธ๋‹ค.")] + public int platformLabelUpright = 1; + [Tooltip("๋ฐœํŒ ํŠธ๋ฆฌ๊ฑฐ ํ•œ ๋ณ€(m) โ€” FI Purchaser ์›๋ณธ์€ ์„ฌ 1์นธ(8 m)์„ ๋ฎ๋Š” ๅ์ž๋ผ ๋ฐœํŒ ํฌ๊ธฐ๋กœ ์ค„์ธ๋‹ค.")] public float platformTriggerSize = 3.4f; @@ -544,8 +548,8 @@ namespace WL.Island [Tooltip("์–ด๋””๊นŒ์ง€ ์น˜๋ €๋Š”์ง€ ์„ธ๋Š” PlayerPrefs ํ‚ค 1๊ฐœ(์‹œ์ž‘๋งˆ๋‹ค ์ดˆ๊ธฐํ™” ๋Œ€์ƒ).")] public string flowStepPrefsKey = "WL_Island_FlowStep"; - [Tooltip("๋ฐœํŒ ๋ฌธ๊ตฌ ์„œ์‹ โ€” {0} = ๋‹จ๊ณ„ ๋ฌธ๊ตฌ ยท {1} = ๊ฐ€๊ฒฉ.")] - public string flowLabelFormat = "{0}\n{1} ๊ณจ๋“œ"; + [Tooltip("๋ฐœํŒ ๋ฌธ๊ตฌ ์„œ์‹ โ€” {0} = ๋‹จ๊ณ„ ๋ฌธ๊ตฌ ยท {1} = ๊ฐ€๊ฒฉ. ๐Ÿ”ด 2026-09-16 PD ใ€Œ๋น„์šฉ(์˜ˆ: 60 ๊ณจ๋“œ)์€ ๋นผ๋ผ โ€” ๋ฐ”๋‹ฅ ๊ฐ€๊ฒฉํŒ์ด ์ด๋ฏธ ๋ณด์—ฌ ์ค€๋‹คใ€ โ†’ ใ€Œ{0}ใ€ ๋งŒ.")] + public string flowLabelFormat = "{0}"; [Tooltip("ํ•œ ์ค„ = ํ•œ ๋‹จ๊ณ„. ์œ„์—์„œ๋ถ€ํ„ฐ ์ฐจ๋ก€๋กœ ๋ฐœํŒ์ด ์„ ๋‹ค.")] public WLFlowStepDef[] flowSteps = new WLFlowStepDef[] diff --git a/Assets/WL/Island/WLIslandUi.cs b/Assets/WL/Island/WLIslandUi.cs index ce61bc989..63f643d8a 100644 --- a/Assets/WL/Island/WLIslandUi.cs +++ b/Assets/WL/Island/WLIslandUi.cs @@ -107,18 +107,28 @@ namespace WL.Island var rt = tmp.GetComponent(); if (rt != null) rt.sizeDelta = new Vector2(6f, 1.4f); - go.AddComponent(); + var bb = go.AddComponent(); + bb.upright = cfg == null || cfg.platformLabelUpright != 0; } } - /// ์ด๋ฆ„ํ‘œ๊ฐ€ ํ•ญ์ƒ ์นด๋ฉ”๋ผ๋ฅผ ๋ณธ๋‹ค. + /// + /// ์ด๋ฆ„ํ‘œ๊ฐ€ ํ•ญ์ƒ ์นด๋ฉ”๋ผ๋ฅผ ๋ณธ๋‹ค. + /// ๐Ÿ”ด 2026-09-16 PD ใ€Œ๊ธ€์ž๋ฅผ ๋ฐ”๋‹ฅ ํŒ๊ณผ 90ยฐ ๋กœ ์„ธ์›Œ ํ”Œ๋ ˆ์ด์–ด ๊ธฐ์ค€ ์ •๋ฉด์œผ๋กœใ€ โ†’ `upright` ๋ฉด **์„ธ์šด ์ฑ„** ์นด๋ฉ”๋ผ ์ชฝ ๋ฐฉํ–ฅ(์ˆ˜ํ‰)๋งŒ ๋”ฐ๋ผ ๋ˆ๋‹ค. + /// public sealed class WLIslandBillboard : MonoBehaviour { + [Tooltip("1 = ์„ธ์šด ์ฑ„ ์ˆ˜ํ‰ ๋ฐฉํ–ฅ๋งŒ ์นด๋ฉ”๋ผ๋ฅผ ๋”ฐ๋ฅธ๋‹ค ยท 0 = ์นด๋ฉ”๋ผ๋ฅผ ์ •๋ฉด์œผ๋กœ ๋ณด๊ฒŒ ๊ธฐ์šธ์ธ๋‹ค.")] + public bool upright = true; + void LateUpdate() { var cam = Camera.main; if (cam == null) return; - transform.rotation = Quaternion.LookRotation(transform.position - cam.transform.position, Vector3.up); + var dir = transform.position - cam.transform.position; + if (upright) dir.y = 0f; + if (dir.sqrMagnitude < 1e-6f) return; + transform.rotation = Quaternion.LookRotation(dir, Vector3.up); } } diff --git a/Assets/WL/Look/Farm/Resources/WL/WLIslandLookSettings.asset b/Assets/WL/Look/Farm/Resources/WL/WLIslandLookSettings.asset index 45052219b..e1cb91117 100644 --- a/Assets/WL/Look/Farm/Resources/WL/WLIslandLookSettings.asset +++ b/Assets/WL/Look/Farm/Resources/WL/WLIslandLookSettings.asset @@ -49,9 +49,15 @@ MonoBehaviour: ambientGround: {r: 0.047, g: 0.043, b: 0.035, a: 1} ambientIntensity: 1 skybox: {fileID: 10304, guid: 0000000000000000f000000000000000, type: 0} - dirLightColor: {r: 1, g: 1, b: 1, a: 1} - dirLightIntensity: 1 + dirLightColor: {r: 1, g: 0.97, b: 0.9, a: 1} + dirLightIntensity: 1.15 dirLightSoftShadows: 1 + dirLightShadowStrength: 0.45 + dirLightEnsure: 1 + islandSsaoOff: 1 + ssaoOffScenes: + - Level01 + - WL_FarmLook fogOff: 1 disableFiGraphicsManager: 1 rescanAtSeconds: @@ -68,7 +74,9 @@ MonoBehaviour: waterMode: 1 waterFrom: {fileID: 2100000, guid: ce0f8fd131f36964dbc918b8c63f838b, type: 2} waterTo: {fileID: 2100000, guid: 7c41d0e5b2a94f6d8e3517c0aa96b41d, type: 2} - shoreFoamEnabled: 0 + shoreFoamEnabled: 1 + shoreFoamUp: 0.08 + shoreFoamDown: 0.25 shoreFoamMaterial: {fileID: 2100000, guid: d24a4c5ce16646c409631c112b57f2cf, type: 2} shoreFoamWidth: 1.6 shoreFoamInner: 0.5 @@ -394,13 +402,28 @@ MonoBehaviour: - Level01 - WL_FarmLook demoTerrainEnabled: 1 + demoCloudStrength: 0.5 + demoCloudDensity: 0.025 terrainHeightRes: 129 terrainAlphaRes: 256 terrainMargin: 8 terrainMaxSize: 128 terrainBlend: 1 terrainBottomY: -1.8 - terrainSlope: 3 + terrainSlope: 5 + terrainSlopeVar: 0.35 + terrainSlopeVarCell: 5 + terrainBumpAmp: 0.25 + terrainBumpCell: 2.5 + terrainSeabedBumpAmp: 0.12 + terrainSeabedBumpCell: 3.5 + terrainReliefAmp: 0.5 + terrainReliefCell: 4.5 + terrainReliefEdgeFade: 2.5 + terrainReliefFlatRadius: 3 + terrainReliefFeather: 1.5 + terrainReliefCollider: 1 + demoTerrainBrightness: 0.12 terrainHeightSpan: 10 terrainRoundHeight: 1 demoTerrainMaterial: {fileID: 2100000, guid: 719df871080742445b86937d1b9977c9, type: 2} diff --git a/Assets/WL/Look/Farm/WLDemoTerrain.cs b/Assets/WL/Look/Farm/WLDemoTerrain.cs index d9d486990..3c0d943e0 100644 --- a/Assets/WL/Look/Farm/WLDemoTerrain.cs +++ b/Assets/WL/Look/Farm/WLDemoTerrain.cs @@ -56,6 +56,12 @@ namespace WL.Look.Farm public WLIslandLookSettings cfg; + /// ๊ตฝ๊ธฐ๊ฐ€ ๋๋‚œ ์งํ›„(๊ฐ’์ด ๋ฐ”๋€Œ์–ด ์‹ค์ œ๋กœ ๋‹ค์‹œ ๊ตฌ์šด ๋•Œ๋งŒ) โ€” ๋ฌผ๊ฐ€ ๊ฑฐํ’ˆ ๋ (WLShoreFoam)๊ฐ€ ๋ฌผ๊ฐ€์„ ์„ ๋‹ค์‹œ ๋”ด๋‹ค. + public static event System.Action Baked; + + /// ์ง€๊ธˆ ํ‘œ์‹œ ์ค‘์ธ ํ„ฐ๋ ˆ์ธ(์—†๊ฑฐ๋‚˜ ์ˆจ๊น€์ด๋ฉด null). ๋ฌผ๊ฐ€ ๊ฑฐํ’ˆ ๋ ๊ฐ€ ๋†’์ด๋ฅผ ์ฝ๋Š”๋‹ค. + public Terrain TerrainComponent { get { return _child != null && _child.activeSelf && _terrain != null && _terrain.terrainData != null ? _terrain : null; } } + GameObject _child; Terrain _terrain; TerrainData _data; @@ -81,6 +87,9 @@ namespace WL.Look.Farm { RestoreTops(); if (_data != null) Destroy(_data); // ์ž์‹ Terrain ๋„ ๊ฐ™์€ ํŒŒ๊ดด์— ์‚ฌ๋ผ์ง„๋‹ค(Destroy ๋Š” ํ”„๋ ˆ์ž„ ๋์— ์‹คํ–‰) + if (_reliefMesh != null) Destroy(_reliefMesh); + foreach (var kv in _tuned) if (kv.Value != null) Destroy(kv.Value); // ๋Ÿฐํƒ€์ž„ ์žฌ์งˆ ๋ณต์‚ฌ๋ณธ(DontSave)์€ ์„ฌ์„ ๋‚˜๊ฐˆ ๋•Œ ํ•จ๊ป˜ ๋ฒ„๋ฆฐ๋‹ค(Play ๋งˆ๋‹ค ์Œ“์ด์ง€ ์•Š๊ฒŒ) + _tuned.Clear(); } /// ๋‹ค์‹œ ๋งŒ๋“ ๋‹ค(์„ฌ ํ™•์žฅยท๋ฐญ ์žฌ๊ตฌ์„ฑยท์žฌํ›‘๊ธฐ). ๋ฉฑ๋“ฑ โ€” ๋ฐœ์ž๊ตญยท๊ตฌ์—ญยท๊ฐ’์ด ๊ทธ๋Œ€๋กœ๋ฉด ์•„๋ฌด๊ฒƒ๋„ ํ•˜์ง€ ์•Š๋Š”๋‹ค. @@ -114,12 +123,16 @@ namespace WL.Look.Farm + zone.center.x * 47f + zone.center.z * 53f + zone.extents.x * 59f + zone.extents.z * 61f + cfg.softDirtExpand * 67f + cfg.softDirtCornerRadius * 71f + cfg.softDirtNoise * 73f + cfg.softDirtNoiseCell * 79f + cfg.softDirtFeather * 83f + cfg.demoFirstLayerDensity * 89f + cfg.demoSecondLayerDensity * 97f + cfg.demoPositionVariance * 101f + cfg.demoScaleVariance * 103f - + ScatterSig(cfg.demoGrass) * 107f + ScatterSig(cfg.demoFlower) * 109f + ScatterSig(cfg.demoGravel) * 113f; + + ScatterSig(cfg.demoGrass) * 107f + ScatterSig(cfg.demoFlower) * 109f + ScatterSig(cfg.demoGravel) * 113f + + cfg.terrainSlopeVar * 127f + cfg.terrainSlopeVarCell * 131f + cfg.terrainBumpAmp * 137f + cfg.terrainBumpCell * 139f + + cfg.terrainSeabedBumpAmp * 149f + cfg.terrainSeabedBumpCell * 151f + + cfg.terrainReliefAmp * 157f + cfg.terrainReliefCell * 163f + cfg.terrainReliefEdgeFade * 167f + cfg.terrainReliefFlatRadius * 173f + + cfg.terrainReliefFeather * 179f + cfg.terrainReliefCollider * 181f + CollectFlatSpots(cfg) * 191f; bool same = !_dirty && _data != null && _child != null && tileSig == _lastTileSig && Mathf.Abs(_lastValSig - valSig) < 0.0001f; HideTops(); // ์ƒˆ๋กœ ์—ด๋ฆฐ ํƒ€์ผ ์œ—๋ฉด๋„ ์ˆจ๊ธด๋‹ค(๋ฉฑ๋“ฑ) - if (same) { _child.SetActive(true); return; } + if (same) { _child.SetActive(true); if (_relief != null) _relief.SetActive(true); return; } var sw = System.Diagnostics.Stopwatch.StartNew(); Bake(size, half, topY, zoneValid, zone); @@ -132,13 +145,16 @@ namespace WL.Look.Farm _lastTileSig = tileSig; _lastValSig = valSig; _dirty = false; Rebuilds++; + if (Baked != null) Baked(); LastLog = "๋ฐ๋ชจ ํ„ฐ๋ ˆ์ธ โ€” ํƒ€์ผ " + _tiles.Count + " ยท ํ…Œ๋‘๋ฆฌ ๋ณ€ " + LastEdges + " ยท ๋ง " + LastRingPoints + " ยท ๋ฒ”์œ„ " + LastSizeX.ToString("F0") + "ร—" + LastSizeZ.ToString("F0") + " m(์›์  " + _child.transform.position.x.ToString("F1") + ", " + _child.transform.position.z.ToString("F1") + ")" + " ยท ๋†’์ด๋งต " + _dataHeightRes + "ยฒ ยท ์Šคํ”Œ๋žซ " + _dataAlphaRes + "ยฒ" + " ยท ์œ—๋ฉด y " + topY.ToString("F2") + (fixedTop ? "(๊ณ ์ •)" : "(์‹ค์ธก)") + " โ†’ ๋ฐ”๋‹ฅ " + cfg.terrainBottomY.ToString("F2") - + " ยท ๊ฒฝ์‚ฌ " + cfg.terrainSlope.ToString("F1") + " m ยท ์ž”๋”” ๊ฒฝ๊ณ„ R " + cfg.skirtGrassRound.ToString("F1") + " ๊ตฝ์ด ยฑ" + cfg.skirtWaveAmp.ToString("F1") + + " ยท ๊ฒฝ์‚ฌ " + cfg.terrainSlope.ToString("F1") + " m(ํญ ยฑ" + (cfg.terrainSlopeVar * 100f).ToString("F0") + " % ยท ๊ตด๊ณก ยฑ" + cfg.terrainBumpAmp.ToString("F2") + " m ยท ๋ฐ”๋‹ฅ ๊ตด๊ณก ยฑ" + cfg.terrainSeabedBumpAmp.ToString("F2") + " m)" + + " ยท ๋‘”๋• 0~" + cfg.terrainReliefAmp.ToString("F2") + " m(๊ฒฉ์ž " + cfg.terrainReliefCell.ToString("F1") + " ยท ํ‰ํ‰ ์ž๋ฆฌ " + _flatX.Count + " ยท ์ฝœ๋ผ์ด๋” " + (LastReliefTris > 0 ? LastReliefTris + "โ–ณ" : "์—†์Œ") + ")" + + " ยท ์ž”๋”” ๊ฒฝ๊ณ„ R " + cfg.skirtGrassRound.ToString("F1") + " ๊ตฝ์ด ยฑ" + cfg.skirtWaveAmp.ToString("F1") + " ยท ๋ฐญ " + (zoneValid ? (zone.size.x).ToString("F1") + "ร—" + (zone.size.z).ToString("F1") + " m" : "๋ฏธํ™•์ •") + " ยท ๊ตฝ๊ธฐ " + LastBakeMs.ToString("F1") + " ms ยท ์ธ์Šคํ„ด์„œ " + LastInstancerMs.ToString("F1") + " ms(" + instLog + ")" + " ยท ๋“œ๋กœ์šฐ์ฝœ ํ„ฐ๋ ˆ์ธ 1(+๊ทธ๋ฆผ์ž ํŒจ์Šค) + ์ธ๋‹ค์ด๋ ‰ํŠธ 3 ยท ํƒ€์ผ ์œ—๋ฉด ์ˆจ๊น€ " + _hiddenTops.Count; @@ -153,10 +169,151 @@ namespace WL.Look.Farm void Hide(string reason) { if (_child != null) _child.SetActive(false); // ์ธ์Šคํ„ด์„œ OnDisable โ†’ ๋ฒ„ํผ ํ•ด์ œ ยท Terrain ์ˆจ๊น€ + if (_relief != null) _relief.SetActive(false); RestoreTops(); LastLog = reason; } + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + // ๐Ÿ”ด 2026-09-16 PD ใ€Œ๋ฐ๋ชจ ๊ฐ™์€ ๊ทธ๋ผ๋ฐ์ด์…˜ ยท ๊ทธ๋ฆผ์ž ๋‹จ๊ณ„ใ€ โ€” ์„ฌ ์œ—๋ฉด ๋‘”๋• + // ํ‰ํ‰ํ•˜๊ฒŒ ๋‘˜ ์ž๋ฆฌ = ํ”Œ๋กœ์šฐ ๋‹จ๊ณ„ ์ž๋ฆฌ(์บ ํ”„ํŒŒ์ด์–ดยท์ƒ์ธยท์‚ฌ๊ณผ๋‚˜๋ฌดยท๋˜์ „ ยท ์บ ํ”„ ์ž๋ฆฌ๋Š” ์ฒœ๋ง‰ ๋ฐ˜์ง€๋ฆ„๊นŒ์ง€) + ๋˜์ „ ์ž…๊ตฌ. + // ์ž๋ฆฌ๋Š” ๊ฐ’(์„ค์ •)์—์„œ ์˜ค๊ณ  ์ž…๊ตฌ๋Š” ์”ฌ์—์„œ ์ฝ๋Š”๋‹ค โ†’ ์„œ๋ช…์— ๋„ฃ์–ด ์ž…๊ตฌ๊ฐ€ ์ƒˆ๋กœ ์ƒ๊ธฐ๋ฉด ํ•œ ๋ฒˆ ๋‹ค์‹œ ๊ตฝ๋Š”๋‹ค(โ‰ˆ100 ms ยท ํ™”๋ฉด ์œ ์ง€). + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + readonly List _flatX = new List(8), _flatZ = new List(8), _flatR = new List(8); + GameObject _relief; + MeshCollider _reliefCol; + Mesh _reliefMesh; + public static int LastReliefTris; + + float CollectFlatSpots(WLIslandLookSettings cfg) + { + _flatX.Clear(); _flatZ.Clear(); _flatR.Clear(); + float sig = 0f; + if (cfg.terrainReliefAmp <= 0f) return 0f; + float r0 = Mathf.Max(0f, cfg.terrainReliefFlatRadius); + var icfg = global::WL.Island.WLIslandSettings.Instance; + if (icfg != null && global::WL.Island.WLIslandFlowV2.Active(icfg)) + { + int n = global::WL.Island.WLIslandFlowV2.StepCount(icfg); + for (int i = 0; i < n; i++) + { + var p = global::WL.Island.WLIslandFlowV2.PosFor(icfg, i); + float r = r0; + if (i == 0 && icfg.campDecorEnabled != 0) r = Mathf.Max(r, icfg.campTentRadius + 1.5f); // ์บ ํ”„ ์ฒœ๋ง‰(campTentRadius)๊นŒ์ง€ ํ‰ํ‰ + AddFlat(p.x, p.z, r, ref sig); + } + } + var gates = Object.FindObjectsByType(FindObjectsInactive.Include, FindObjectsSortMode.None); + for (int i = 0; i < gates.Length; i++) + { + if (gates[i] == null) continue; + var p = gates[i].transform.position; + AddFlat(p.x, p.z, r0, ref sig); + } + return sig; + } + + void AddFlat(float x, float z, float r, ref float sig) + { + _flatX.Add(x); _flatZ.Add(z); _flatR.Add(r); + sig += Mathf.Round(x * 4f) * 0.37f + Mathf.Round(z * 4f) * 0.59f + r * 0.11f; + } + + /// ๋‘”๋• ๋ด‰ํˆฌ 0~1 โ€” ์ž”๋”” ๊ฒฝ๊ณ„ ๊ทผ์ฒ˜ ยท ๋ฐญ ๊ตฌ์—ญ ยท ํ‰ํ‰ ์ž๋ฆฌ ๋‘˜๋ ˆ์—์„œ 0. + float ReliefEnv(float x, float z, float s, float edgeFade, float feather, bool zoneValid, float zcx, float zcz, float zhx, float zhz, float zrad, float znoise, float zcell) + { + float env = SmoothStep01(-s / edgeFade); + if (env <= 0f) return 0f; + if (zoneValid) + { + float dz = WLSoftDirt.ShapeDist(x, z, zcx, zcz, zhx, zhz, zrad, znoise, zcell); // โˆ’ = ๋ฐญ ์•ˆ ยท + = ๋ฐ–(m) + env *= SmoothStep01((dz - 0.5f) / feather); + if (env <= 0f) return 0f; + } + for (int q = 0; q < _flatX.Count; q++) + { + float dx = x - _flatX[q], dzq = z - _flatZ[q]; + env *= SmoothStep01((Mathf.Sqrt(dx * dx + dzq * dzq) - _flatR[q]) / feather); + if (env <= 0f) return 0f; + } + return env; + } + + /// + /// ๋ฐœ์ž๊ตญ ์•ˆ(ํƒ€์ผ ๋„ค๋ชจ ยท s โ‰ค 0 ์ธ ๊ฒฉ์ž์ ๋งŒ)์˜ ๋‘”๋• ๋†’์ด๋ฅผ ๋”ฐ๋ฅด๋Š” MeshCollider โ€” ํ”Œ๋ ˆ์ด์–ด๊ฐ€ ๋‘”๋• ์œ„๋ฅผ ๊ฑท๋Š”๋‹ค. + /// ๊ฒฉ์ž โ‰ฅ 0.5 m(16 m ์„ฌ โ‰ˆ 2์ฒœ ์‚ผ๊ฐํ˜•). ๋ ˆ์ด์–ด๋Š” FI ํƒ€์ผ๊ณผ ๊ฐ™๊ฒŒ(FI ๊ด‘์„  ํŒ์ • ๋ถˆ๋ณ€). ๋ฐœ์ž๊ตญ ๋ฐ–(ํ•ด๋ณ€)์€ ์ฝœ๋ผ์ด๋” ์—†์Œ = ๊ฐ€์žฅ์ž๋ฆฌ ๋™์ž‘ ๋ถˆ๋ณ€. + /// + void BuildReliefCollider(bool on, float ox, float oz, float w, float l, float baseY, float span, float[,] heights, int hRes, float half, float stepX, float stepZ) + { + LastReliefTris = 0; + if (!on) + { + if (_relief != null) _relief.SetActive(false); + return; + } + if (_relief == null) + { + var t = transform.Find("~WL_ReliefCollider"); + _relief = t != null ? t.gameObject : new GameObject("~WL_ReliefCollider"); + _relief.transform.SetParent(transform, false); + _relief.transform.SetPositionAndRotation(Vector3.zero, Quaternion.identity); // ๋ฉ”์‹œ๋Š” ์›”๋“œ ์ขŒํ‘œ + _reliefCol = _relief.GetComponent(); + if (_reliefCol == null) _reliefCol = _relief.AddComponent(); + } + if (_tileTops.Count > 0 && _tileTops[0] != null) _relief.layer = _tileTops[0].gameObject.layer; + if (_reliefMesh == null) _reliefMesh = new Mesh { name = "WL_ReliefCollider", hideFlags = HideFlags.DontSave }; + + float minX = float.MaxValue, minZ = float.MaxValue, maxX = float.MinValue, maxZ = float.MinValue; + for (int i = 0; i < _tiles.Count; i++) + { + var t = _tiles[i]; + if (t.x - half < minX) minX = t.x - half; + if (t.x + half > maxX) maxX = t.x + half; + if (t.z - half < minZ) minZ = t.z - half; + if (t.z + half > maxZ) maxZ = t.z + half; + } + float cell = Mathf.Max(0.5f, Mathf.Max(stepX, stepZ)); + int nx = Mathf.Clamp(Mathf.RoundToInt((maxX - minX) / cell) + 1, 2, 1025), nz = Mathf.Clamp(Mathf.RoundToInt((maxZ - minZ) / cell) + 1, 2, 1025); + float cx = (maxX - minX) / (nx - 1), cz = (maxZ - minZ) / (nz - 1); + var verts = new List(nx * nz); + var vmap = new int[nx * nz]; + var tris = new List(nx * nz * 6); + for (int k = 0; k < vmap.Length; k++) vmap[k] = -1; + for (int j = 0; j < nz; j++) + { + float z = minZ + j * cz; + for (int i = 0; i < nx; i++) + { + float x = minX + i * cx; + if (!Inside(x, z, half + 0.001f)) continue; + // ๋†’์ด๋งต ๊ฒน์„ ํ˜•(์ •๊ทœํ™” โ†’ ์›”๋“œ) + float gx = Mathf.Clamp((x - ox) / stepX, 0f, hRes - 1.0001f), gz = Mathf.Clamp((z - oz) / stepZ, 0f, hRes - 1.0001f); + int gi = (int)gx, gj = (int)gz; float tx = gx - gi, tz = gz - gj; + float h = Mathf.Lerp(Mathf.Lerp(heights[gj, gi], heights[gj, gi + 1], tx), Mathf.Lerp(heights[gj + 1, gi], heights[gj + 1, gi + 1], tx), tz); + vmap[j * nx + i] = verts.Count; + verts.Add(new Vector3(x, baseY + h * span, z)); + } + } + for (int j = 0; j < nz - 1; j++) + for (int i = 0; i < nx - 1; i++) + { + int a = vmap[j * nx + i], b = vmap[j * nx + i + 1], c = vmap[(j + 1) * nx + i], d = vmap[(j + 1) * nx + i + 1]; + if (a < 0 || b < 0 || c < 0 || d < 0) continue; + tris.Add(a); tris.Add(c); tris.Add(b); + tris.Add(b); tris.Add(c); tris.Add(d); + } + _reliefMesh.Clear(); + if (tris.Count == 0) { _relief.SetActive(false); return; } + _reliefMesh.indexFormat = verts.Count > 65000 ? UnityEngine.Rendering.IndexFormat.UInt32 : UnityEngine.Rendering.IndexFormat.UInt16; + _reliefMesh.SetVertices(verts); + _reliefMesh.SetTriangles(tris, 0); + _reliefMesh.RecalculateBounds(); + _reliefCol.sharedMesh = null; // ๊ฐ™์€ ๋ฉ”์‹œ ์žฌ๋Œ€์ž…์€ ๋‹ค์‹œ ๊ตฝ์ง€ ์•Š๋Š”๋‹ค โ†’ ๋น„์› ๋‹ค๊ฐ€ ๋ฌผ๋ฆฐ๋‹ค + _reliefCol.sharedMesh = _reliefMesh; + _relief.SetActive(true); + LastReliefTris = tris.Count / 3; + } + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ // ์ž…๋ ฅ ์ˆ˜์ง‘ โ€” WLShoreSkirt ์™€ ๊ฐ™์€ ํŒ๋ณ„ // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ @@ -264,15 +421,22 @@ namespace WL.Look.Farm return (h & 0xffffu) / 65535f; } - /// ๊ฒฝ๊ณ„ ๊ตฝ์ด โ€” ๊ฐ’ ๋…ธ์ด์ฆˆ(๊ฒฉ์ž skirtWaveCell ยท ๊ฒน์„ ํ˜• ยท ๊ฒฐ์ •์ ) ร— ์ง„ํญ. ์ง์„  ๋ณ€์„ ํŒŒ๋„์ฒ˜๋Ÿผ ๊ตฝ์ด์น˜๊ฒŒ ํ•œ๋‹ค(WLShoreSkirt ์™€ ๊ฐ™์€ ์‹). + /// ๊ฐ’ ๋…ธ์ด์ฆˆ โˆ’1~+1(๊ฒฉ์ž cell ยท ๊ฒน์„ ํ˜• ยท ๊ฒฐ์ •์ ). seed 0 = ๊ฒฝ๊ณ„ ๊ตฝ์ด(WLShoreSkirt ์™€ ๊ฐ™์€ ์‹) ยท ๋‹ค๋ฅธ seed ๋Š” ๊ฒฉ์ž๋ฅผ ์˜ฎ๊ฒจ ๋…๋ฆฝ๋œ ๋ฌด๋Šฌ. + static float ValueNoise(float x, float z, float cell, int seed) + { + if (cell <= 0.1f) return 0f; + float qx = x / cell, qz = z / cell; + int gx = Mathf.FloorToInt(qx) + seed * 1013, gz = Mathf.FloorToInt(qz) + seed * 2027; float fx = qx - Mathf.Floor(qx), fz = qz - Mathf.Floor(qz); + fx = fx * fx * (3f - 2f * fx); fz = fz * fz * (3f - 2f * fz); + float v00 = Hash01(gx, gz), v10 = Hash01(gx + 1, gz), v01 = Hash01(gx, gz + 1), v11 = Hash01(gx + 1, gz + 1); + return (Mathf.Lerp(Mathf.Lerp(v00, v10, fx), Mathf.Lerp(v01, v11, fx), fz) - 0.5f) * 2f; + } + + /// ๊ฒฝ๊ณ„ ๊ตฝ์ด โ€” ๊ฐ’ ๋…ธ์ด์ฆˆ(๊ฒฉ์ž skirtWaveCell) ร— ์ง„ํญ. ์ง์„  ๋ณ€์„ ํŒŒ๋„์ฒ˜๋Ÿผ ๊ตฝ์ด์น˜๊ฒŒ ํ•œ๋‹ค(WLShoreSkirt ์™€ ๊ฐ™์€ ์‹). float Wave(float x, float z) { if (cfg.skirtWaveAmp <= 0f || cfg.skirtWaveCell <= 0.1f) return 0f; - float qx = x / cfg.skirtWaveCell, qz = z / cfg.skirtWaveCell; - int gx = Mathf.FloorToInt(qx), gz = Mathf.FloorToInt(qz); float fx = qx - gx, fz = qz - gz; - fx = fx * fx * (3f - 2f * fx); fz = fz * fz * (3f - 2f * fz); - float v00 = Hash01(gx, gz), v10 = Hash01(gx + 1, gz), v01 = Hash01(gx, gz + 1), v11 = Hash01(gx + 1, gz + 1); - return (Mathf.Lerp(Mathf.Lerp(v00, v10, fx), Mathf.Lerp(v01, v11, fx), fz) - 0.5f) * 2f * cfg.skirtWaveAmp; + return ValueNoise(x, z, cfg.skirtWaveCell, 0) * cfg.skirtWaveAmp; } /// @@ -350,10 +514,16 @@ namespace WL.Look.Farm float l = Mathf.Min((maxZ - minZ) + 2f * margin, maxSize); float ox = (minX + maxX) * 0.5f - w * 0.5f, oz = (minZ + maxZ) * 0.5f - l * 0.5f; float bottom = cfg.terrainBottomY; - float span = Mathf.Max(cfg.terrainHeightSpan, topY - bottom); // ๋†’์ด๋งต 1.0 = bottom + span โ‰ฅ ์œ—๋ฉด - if (span <= 0.001f) span = 1f; float slope = Mathf.Max(0.01f, cfg.terrainSlope); float blend = Mathf.Max(0.01f, cfg.terrainBlend); + // ๐Ÿ”ด 2026-09-16 PD ใ€Œ๊ฐ€์žฅ์ž๋ฆฌ โ†’ ๋ฐ”๋‹ค๋ฅผ ์ž์—ฐ์Šค๋Ÿฌ์šด ๊ฒฝ์‚ฌ๋ฉด์œผ๋กœ ยท ๊ตด๊ณก์ด ๋ณด์ด๊ฒŒใ€ โ€” ๊ฒฝ์‚ฌ ํญ ํ”๋“ค๊ธฐ + ๊ฒฝ์‚ฌ๋ฉด ๋Šฅ์„ /๊ณจ + ๋ฐ”๋‹ค ๋ฐ”๋‹ฅ ๊ตด๊ณก(๊ฐ’ ๋…ธ์ด์ฆˆ ยท ๊ฒฐ์ •์ ) + float slopeVar = Mathf.Clamp(cfg.terrainSlopeVar, 0f, 0.8f), varCell = Mathf.Max(0.5f, cfg.terrainSlopeVarCell); + float bumpAmp = Mathf.Max(0f, cfg.terrainBumpAmp), bumpCell = Mathf.Max(0.5f, cfg.terrainBumpCell); + float seaAmp = Mathf.Max(0f, cfg.terrainSeabedBumpAmp), seaCell = Mathf.Max(0.5f, cfg.terrainSeabedBumpCell); + const float seaFade = 2f; // ๊ฒฝ์‚ฌ ๋์—์„œ ๋ฐ”๋‹ค ๊ตด๊ณก์ด ์ž๋ผ๋Š” ๊ธธ์ด(m) + float baseY = bottom - seaAmp - bumpAmp; // ๋†’์ด๋งต 0 = ๊ฐ€์žฅ ๊นŠ์€ ๊ณจ๋ณด๋‹ค ์•„๋ž˜ + float span = Mathf.Max(cfg.terrainHeightSpan, topY - baseY + 0.5f); // ๋†’์ด๋งต 1.0 = baseY + span โ‰ฅ ์œ—๋ฉด + if (span <= 0.001f) span = 1f; int hRes = HeightRes(cfg.terrainHeightRes); int aRes = AlphaRes(cfg.terrainAlphaRes); LastSizeX = w; LastSizeZ = l; @@ -362,14 +532,14 @@ namespace WL.Look.Farm EnsureData(hRes, aRes); var newSize = new Vector3(w, span, l); if ((_data.size - newSize).sqrMagnitude > 1e-6f) _data.size = newSize; // ๋ฐ”๋€” ๋•Œ๋งŒ(์žฌ๋Œ€์ž… = ๋‚ด๋ถ€ ์žฌ์ƒ์„ฑ โ†’ ํ”„๋ ˆ์ž„ ๋น”) - var newPos = new Vector3(ox, bottom, oz); + var newPos = new Vector3(ox, baseY, oz); if ((_child.transform.position - newPos).sqrMagnitude > 1e-6f) _child.transform.position = newPos; // 3) ๊ฑฐ๋ฆฌ์žฅ โ€” ํ…Œ๋‘๋ฆฌ ์„ ๋ถ„ ยท ์—ด๊ธฐ ์—ฐ์‚ฐ ๋ง(ํƒ€์ผ ๊ฐ€์žฅ์ž๋ฆฌ์— ๋งž์ถ˜ ๊ฒฉ์ž ยท ์นธ โ‰ค ์Šคํ”Œ๋žซ ํ…์…€) BuildEdges(size, half); float round = Mathf.Max(0f, cfg.skirtGrassRound); float cellA = Mathf.Max(w / aRes, l / aRes); - float far = slope + blend + Mathf.Max(0f, cfg.skirtWaveAmp) + cellA * 2f; // ์ด๋ณด๋‹ค ๋จผ ๋ฐ”๊นฅ์€ ๋†’์ดยท์Šคํ”Œ๋žซ ๋ชจ๋‘ ์ƒ์ˆ˜ + float far = slope * (1f + slopeVar) + seaFade + blend + Mathf.Max(0f, cfg.skirtWaveAmp) + cellA * 2f; // ์ด๋ณด๋‹ค ๋จผ ๋ฐ”๊นฅ์€ ๋‘ฅ๊ธ€๋ฆผ์ด ๋†’์ดยท์Šคํ”Œ๋žซ์— ์˜ํ–ฅ ์—†์Œ // ๐Ÿ”ด 2026-09-16 Lead ์‹ค์ธก: ๋ง 0.125 m ๊ฐ„๊ฒฉ(632์ ) ร— ์Šคํ”Œ๋žซ 65k ํ…์…€ = ๊ตฝ๊ธฐ 851 ms โ†’ ์ƒ์ธ ์„ค์น˜ ๋•Œ ํ™”๋ฉด์ด ๋ฉŽ์–ด ใ€Œ์ง€ํ˜•์ด ์‚ฌ๋ผ์กŒ๋‹ค ๋ณด์ž„ใ€. // ๋ง์€ 0.5 m ๊ฐ„๊ฒฉ(โ‰ˆ1/4)์œผ๋กœ, ๊ฒฝ๊ณ„ ๊ฑฐ๋ฆฌ s ๋Š” ๋†’์ด๋งต ๊ฒฉ์ž(0.25 m)์—์„œ ํ•œ ๋ฒˆ๋งŒ ๊ณ„์‚ฐํ•ด ์Šคํ”Œ๋žซ์€ ๊ฒน์„ ํ˜•์œผ๋กœ ์ฝ๋Š”๋‹ค(blend 1 m ์— ์ถฉ๋ถ„). GatherRing(size, half, minX, minZ, maxX, maxZ, Mathf.Max(0.5f, Mathf.Min(w / aRes, l / aRes)), round); @@ -380,22 +550,8 @@ namespace WL.Look.Farm var heights = new float[hRes, hRes]; var sGrid = new float[hRes * hRes]; // ๋‘ฅ๊ธ€๋ฆผ+๊ตฝ์ด ๊ฒฝ๊ณ„ ๊ฑฐ๋ฆฌ(์Šคํ”Œ๋žซ์ด ์ฝ๋Š”๋‹ค) float stepX = w / (hRes - 1), stepZ = l / (hRes - 1); - for (int j = 0; j < hRes; j++) - { - float z = oz + j * stepZ; - for (int i = 0; i < hRes; i++) - { - float x = ox + i * stepX; - float d = RawDist(x, z, half); - float sb = Boundary(x, z, d, round, far); - sGrid[j * hRes + i] = sb; - float s = roundHeight ? sb : d; - float y = s <= 0f ? topY : s >= slope ? bottom : Mathf.Lerp(topY, bottom, SmoothStep01(s / slope)); - heights[j, i] = Mathf.Clamp01((y - bottom) / span); - } - } - // 5) ์Šคํ”Œ๋žซ โ€” ์ธต0 ์ž”๋”” = ๋‘ฅ๊ธ€๋ฆฐ ๋ฐœ์ž๊ตญ ์•ˆ(๊ฒฝ๊ณ„ blend ์— ๊ฑธ์ณ 1โ†’0) ยท ์ธต1 ๋ชจ๋ž˜ = ๋‚˜๋จธ์ง€ + ๋ฐญ ๊ตฌ์—ญ(WLSoftDirt ์™€ ๊ฐ™์€ ๋‘ฅ๊ทผ+๋…ธ์ด์ฆˆ ๊ฒฝ๊ณ„) + // ๋ฐญ ๊ตฌ์—ญ(WLSoftDirt ์™€ ๊ฐ™์€ ๋‘ฅ๊ทผ+๋…ธ์ด์ฆˆ ๊ฒฝ๊ณ„) โ€” ์Šคํ”Œ๋žซ(๋ชจ๋ž˜)๊ณผ ๋‘”๋• ๋ด‰ํˆฌ(ํ‰ํ‰)๊ฐ€ ํ•จ๊ป˜ ์ฝ๋Š”๋‹ค float zcx = 0f, zcz = 0f, zhx = 0f, zhz = 0f, zrad = 0f, znoise = 0f, zcell = 1f, feather = 1f; if (zoneValid) { @@ -408,6 +564,54 @@ namespace WL.Look.Farm feather = Mathf.Max(0.05f, cfg.softDirtFeather); if (zhx <= 0.01f || zhz <= 0.01f) zoneValid = false; } + // ๐Ÿ”ด 2026-09-16 PD ใ€Œ๋ฐ๋ชจ ๊ฐ™์€ ๊ทธ๋ผ๋ฐ์ด์…˜ ยท ๊ทธ๋ฆผ์ž ๋‹จ๊ณ„ใ€ โ€” ์„ฌ ์œ—๋ฉด ๋‘”๋•(์œ„๋กœ๋งŒ 0~reliefAmp ยท ๊ฐ’ ๋…ธ์ด์ฆˆ ยท ํ‰ํ‰ ์ž๋ฆฌ ์ œ์™ธ) + float reliefAmp = Mathf.Max(0f, cfg.terrainReliefAmp), reliefCell = Mathf.Max(1f, cfg.terrainReliefCell); + float reliefFade = Mathf.Max(0.1f, cfg.terrainReliefEdgeFade), reliefFeather = Mathf.Max(0.1f, cfg.terrainReliefFeather); + + for (int j = 0; j < hRes; j++) + { + float z = oz + j * stepZ; + for (int i = 0; i < hRes; i++) + { + float x = ox + i * stepX; + float d = RawDist(x, z, half); + float sb = Boundary(x, z, d, round, far); + sGrid[j * hRes + i] = sb; + float s = roundHeight ? sb : d; + float y; + if (s <= 0f) + { + y = topY; // ๋ฐœ์ž๊ตญ ์•ˆ = FI ์ฝœ๋ผ์ด๋” ์œ—๋ฉด(๋‘”๋•์€ ๊ทธ ์œ„๋กœ๋งŒ) + if (reliefAmp > 0f) + { + float env = ReliefEnv(x, z, s, reliefFade, reliefFeather, zoneValid, zcx, zcz, zhx, zhz, zrad, znoise, zcell); + if (env > 0f) y += env * reliefAmp * (ValueNoise(x, z, reliefCell, 4) * 0.5f + 0.5f); + } + } + else + { + // ๊ฒฝ์‚ฌ ํญ์„ ์ž๋ฆฌ๋งˆ๋‹ค ยฑslopeVar ๋กœ ํ”๋“ ๋‹ค โ†’ ํ•ด์•ˆ์ด ๋„“์–ด์กŒ๋‹ค ์ข์•„์กŒ๋‹ค ํ•œ๋‹ค(๊ตฝ์ด์™€ ๋ณ„๊ฐœ์˜ ๋ฌด๋Šฌ). + float slopeL = slope * (1f + slopeVar * ValueNoise(x, z, varCell, 1)); + if (slopeL < 0.5f) slopeL = 0.5f; + float t = s / slopeL; + if (t < 1f) + { + y = Mathf.Lerp(topY, bottom, SmoothStep01(t)); + // ๋Šฅ์„ ยท๊ณจ โ€” sin(ฯ€t) ๋กœ ์–‘ ๋(์œ—๋ฉดยท๋ฐ”๋‹ฅ)์—์„œ 0 โ†’ ๊ฑท๋Š” ๋ฉด ์œ„๋กœ ์†Ÿ์ง€ ์•Š๊ณ  ๋ฐ”๋‹ฅ๊ณผ๋„ ์ด์–ด์ง„๋‹ค. + if (bumpAmp > 0f) y += Mathf.Sin(t * Mathf.PI) * bumpAmp * ValueNoise(x, z, bumpCell, 2); + } + else + { + y = bottom; + // ๋ฐ”๋‹ค ๋ฐ”๋‹ฅ โ€” ๊ฒฝ์‚ฌ ๋์—์„œ seaFade ์— ๊ฑธ์ณ ๊ตด๊ณก์ด ์ž๋ž€๋‹ค(๋์  ์—ฐ์†). + if (seaAmp > 0f) y += SmoothStep01((s - slopeL) / seaFade) * seaAmp * ValueNoise(x, z, seaCell, 3); + } + } + heights[j, i] = Mathf.Clamp01((y - baseY) / span); + } + } + + // 5) ์Šคํ”Œ๋žซ โ€” ์ธต0 ์ž”๋”” = ๋‘ฅ๊ธ€๋ฆฐ ๋ฐœ์ž๊ตญ ์•ˆ(๊ฒฝ๊ณ„ blend ์— ๊ฑธ์ณ 1โ†’0) ยท ์ธต1 ๋ชจ๋ž˜ = ๋‚˜๋จธ์ง€ + ๋ฐญ ๊ตฌ์—ญ(WLSoftDirt ์™€ ๊ฐ™์€ ๋‘ฅ๊ทผ+๋…ธ์ด์ฆˆ ๊ฒฝ๊ณ„) var alpha = new float[aRes, aRes, 2]; for (int j = 0; j < aRes; j++) { @@ -435,6 +639,7 @@ namespace WL.Look.Farm _data.SetHeights(0, 0, heights); _data.SetAlphamaps(0, 0, alpha); + BuildReliefCollider(reliefAmp > 0f && cfg.terrainReliefCollider != 0, ox, oz, w, l, baseY, span, heights, hRes, half, stepX, stepZ); Bakes++; } @@ -549,14 +754,19 @@ namespace WL.Look.Farm Material Tuned(Material src) { if (src == null) return null; - if (cfg == null || cfg.demoCloudStrength <= 0f || !src.HasProperty("_Cloud_Strength")) return src; + bool strength = cfg != null && cfg.demoCloudStrength > 0f && src.HasProperty("_Cloud_Strength"); + bool density = cfg != null && cfg.demoCloudDensity > 0f && src.HasProperty("_Cloud_Density"); + bool bright = cfg != null && cfg.demoTerrainBrightness > 0f && src.HasProperty("_Brightness"); + if (!strength && !density && !bright) return src; Material m; if (!_tuned.TryGetValue(src, out m) || m == null) { - m = new Material(src) { name = src.name + "(WL cloud " + cfg.demoCloudStrength.ToString("F2") + ")", hideFlags = HideFlags.DontSave }; + m = new Material(src) { name = src.name + "(WL cloud)", hideFlags = HideFlags.DontSave }; _tuned[src] = m; } - if (!Mathf.Approximately(m.GetFloat("_Cloud_Strength"), cfg.demoCloudStrength)) m.SetFloat("_Cloud_Strength", cfg.demoCloudStrength); + if (strength && !Mathf.Approximately(m.GetFloat("_Cloud_Strength"), cfg.demoCloudStrength)) m.SetFloat("_Cloud_Strength", cfg.demoCloudStrength); + if (density && !Mathf.Approximately(m.GetFloat("_Cloud_Density"), cfg.demoCloudDensity)) m.SetFloat("_Cloud_Density", cfg.demoCloudDensity); + if (bright && !Mathf.Approximately(m.GetFloat("_Brightness"), cfg.demoTerrainBrightness)) m.SetFloat("_Brightness", cfg.demoTerrainBrightness); return m; } } diff --git a/Assets/WL/Look/Farm/WLIslandLook.cs b/Assets/WL/Look/Farm/WLIslandLook.cs index 2e86ca8f6..1a7146bd3 100644 --- a/Assets/WL/Look/Farm/WLIslandLook.cs +++ b/Assets/WL/Look/Farm/WLIslandLook.cs @@ -563,17 +563,39 @@ namespace WL.Look.Farm } var lights = Object.FindObjectsByType(FindObjectsInactive.Exclude, FindObjectsSortMode.None); + int suns = 0; for (int i = 0; i < lights.Length; i++) { var l = lights[i]; if (l == null || l.gameObject.scene != scene) continue; - if (l.type != LightType.Directional) continue; - l.color = cfg.dirLightColor; - l.intensity = cfg.dirLightIntensity; - if (cfg.dirLightSoftShadows != 0 && l.shadows != LightShadows.None) l.shadows = LightShadows.Soft; - RenderSettings.sun = l; - LightingApplied++; + if (l.type != LightType.Directional || !l.enabled) continue; + ApplySun(cfg, l); + suns++; } + // ๐Ÿ”ด 2026-09-16 PD ใ€Œ์ž์—ฐ์Šค๋Ÿฌ์šด ๊ด‘์›(ํƒœ์–‘)์„ ๋ฐฐ์น˜ใ€ โ€” ์„ฌ ์”ฌ์— ์ผœ์ง„ ํƒœ์–‘์ด ์—†์„ ๋•Œ๋งŒ ํ•˜๋‚˜ ๋งŒ๋“ ๋‹ค(์žˆ์œผ๋ฉด ๊ทธ ๊ฐ’๋งŒ ๋งž์ถ˜๋‹ค ยท ์”ฌ ํŒŒ์ผ 0์ค„). + if (suns == 0 && cfg.dirLightEnsure != 0) + { + var go = new GameObject("~WL_Sun"); + if (scene.IsValid()) SceneManager.MoveGameObjectToScene(go, scene); + go.transform.rotation = Quaternion.Euler(50f, 330f, 0f); // ๋ฐ๋ชจ Demo.unity ์˜ ํƒœ์–‘ ํšŒ์ „ + var l = go.AddComponent(); + l.type = LightType.Directional; + l.shadows = LightShadows.Soft; + ApplySun(cfg, l); + SunCreated++; + } + } + + public static int SunCreated; + + static void ApplySun(WLIslandLookSettings cfg, Light l) + { + l.color = cfg.dirLightColor; + l.intensity = cfg.dirLightIntensity; + if (cfg.dirLightSoftShadows != 0 && l.shadows != LightShadows.None) l.shadows = LightShadows.Soft; + if (cfg.dirLightShadowStrength >= 0f) l.shadowStrength = Mathf.Clamp01(cfg.dirLightShadowStrength); + RenderSettings.sun = l; + LightingApplied++; } // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ diff --git a/Assets/WL/Look/Farm/WLIslandLookSettings.cs b/Assets/WL/Look/Farm/WLIslandLookSettings.cs index 3aee9e568..f18602380 100644 --- a/Assets/WL/Look/Farm/WLIslandLookSettings.cs +++ b/Assets/WL/Look/Farm/WLIslandLookSettings.cs @@ -189,10 +189,23 @@ namespace WL.Look.Farm public float ambientIntensity = 1f; [Tooltip("๋นŒํŠธ์ธ Default-Skybox. ๋น„์šฐ๋ฉด ์Šค์นด์ด๋ฐ•์Šค๋Š” ์•ˆ ๊ฑด๋“œ๋ฆฐ๋‹ค.")] public Material skybox; - public Color dirLightColor = Color.white; - public float dirLightIntensity = 1f; + [Tooltip("ํƒœ์–‘(๋””๋ ‰์…”๋„) ์ƒ‰. 2026-09-16 PD ใ€Œ๊ด‘์›์ด ๋ถ€์กฑ โ€” ์ž์—ฐ์Šค๋Ÿฌ์šด ํƒœ์–‘์œผ๋กœ ํ™”์‚ฌํ•˜๊ฒŒใ€ โ†’ ์‚ด์ง ๋”ฐ๋œปํ•œ ํฐ์ƒ‰.")] + public Color dirLightColor = new Color(1f, 0.97f, 0.9f, 1f); + [Tooltip("ํƒœ์–‘ ์„ธ๊ธฐ. ๋ฐ๋ชจ 1.0 โ†’ PD ใ€Œํ™”์‚ฌํ•˜๊ฒŒใ€ 1.15(SSAO ๋ฅผ ๋ˆ ๋’ค ๊ธฐ์ค€ ยท ์ž”๋”” ํ•˜์ด๋ผ์ดํŠธ๊ฐ€ ํฐ์ƒ‰์œผ๋กœ ๋‚ ๋ฆฌ์ง€ ์•Š๋Š” ์ƒํ•œ ๊ทผ์ฒ˜).")] + public float dirLightIntensity = 1.15f; [Tooltip("1 = Soft(๋ฐ๋ชจ) ยท 0 = ์›๋ณธ(Hard) ์œ ์ง€.")] public int dirLightSoftShadows = 1; + [Tooltip("ํƒœ์–‘ ๊ทธ๋ฆผ์ž ์ง„ํ•˜๊ธฐ(0~1). ์Œ์ˆ˜ = ์”ฌ ๊ฐ’ ๊ทธ๋Œ€๋กœ(FI ์›๋ณธ 0.5). 2026-09-16 PD ใ€Œํ™”์‚ฌํ•˜๊ฒŒใ€ โ†’ 0.45.")] + public float dirLightShadowStrength = 0.45f; + [Tooltip("์„ฌ ์”ฌ์— ์ผœ์ง„ ๋””๋ ‰์…”๋„ ๋ผ์ดํŠธ๊ฐ€ ํ•˜๋‚˜๋„ ์—†์œผ๋ฉด `~WL_Sun` ์„ ๋งŒ๋“ค์–ด ์œ„ ๊ฐ’์œผ๋กœ ๋‘”๋‹ค(ํšŒ์ „ = ๋ฐ๋ชจ 50/330/0). 0 = ๋งŒ๋“ค์ง€ ์•Š์Œ.")] + public int dirLightEnsure = 1; + + // โ”€โ”€ 2026-09-16 PD ใ€Œํ’€์ด ์–ผ๋ฃฉ๋œ๋ฃฉ โ€” ์›์ธ์„ ๋” ์ฐพ์•„๋ผใ€ โ†’ A/B ์‹ค์ธก: SSAO ๊ฐ€ ํ’€์žŽ๋งˆ๋‹ค ๋ฐœ๋ฐ‘ ๊ทธ๋Š˜์„ ๋งŒ๋“  ๊ฒƒ(๊ตญ์†Œ ์งˆ๊ฐ 0.70 โ†’ 0.07) + [Header("ยง0-e โ€” ์„ฌ์—์„œ๋Š” SSAO ๋ฅผ ๋ˆ๋‹ค (๋ Œ๋”๋Ÿฌ ์—์…‹ 0์ค„ ยท ๋‚˜๊ฐˆ ๋•Œ ๋ณต์›)")] + [Tooltip("1 = ์•„๋ž˜ ์”ฌ์ด ๋–  ์žˆ๋Š” ๋™์•ˆ URP ๋ Œ๋”๋Ÿฌ์˜ SSAO ํ”ผ์ฒ˜๋ฅผ ๋ˆ๋‹ค(์ „ํˆฌ ๋งต์€ ๊ทธ๋Œ€๋กœ). 0 = ์†๋Œ€์ง€ ์•Š์Œ.")] + public int islandSsaoOff = 1; + [Tooltip("SSAO ๋ฅผ ๋Œ ์”ฌ ์ด๋ฆ„(์„ฌ๋งŒ ยท ๋˜์ „์€ ์•„๋ ˆ๋‚˜ ๋ฃฉ ๊ทธ๋Œ€๋กœ).")] + public string[] ssaoOffScenes = new[] { "Level01", "WL_FarmLook" }; [Tooltip("1 ์ด๋ฉด ์•ˆ๊ฐœ๋ฅผ ๋ˆ๋‹ค(๋ฐ๋ชจ = off).")] public int fogOff = 1; @@ -259,6 +272,13 @@ namespace WL.Look.Farm [Tooltip("๊ฑฐํ’ˆ์ด ํ๋ฅด๋Š” ์†๋„(1 = ์ดˆ๋‹น ๋ฌด๋Šฌ 1์นธ). 0 ์ด๋ฉด ์ •์ง€.")] public float shoreFoamScroll = 0.06f; + // โ”€โ”€ 2026-09-16 PD ใ€Œ๋ฌผ์ด ์„ฌ ๊ฒฝ๊ณ„์— ๋‹ฟ์„ ๋•Œ ์ž์—ฐ์Šค๋Ÿฌ์šด ๋ฌผ๋ณด๋ผ๊ฐ€ ์•ˆ ๋ณด์ธ๋‹คใ€ โ€” ๋ฐ๋ชจ ํ„ฐ๋ ˆ์ธ ๋ชจ๋“œ์—์„œ๋Š” ํƒ€์ผ ๋ฐœ์ž๊ตญ ๋Œ€์‹  **ํ„ฐ๋ ˆ์ธ ๋ฌผ๊ฐ€์„ **(์ง€ํ˜• ๋†’์ด = ๋ฌผ ๋†’์ด)์„ ๋”ฐ๋ฅธ๋‹ค + [Tooltip("๋ฐ๋ชจ ํ„ฐ๋ ˆ์ธ ๋ชจ๋“œ์˜ ๋ฌผ๊ฐ€ ๋  โ€” ๋ฌผ๊ฐ€์„ ์—์„œ **๋ชจ๋ž˜ ์ชฝ**์œผ๋กœ ์ง€ํ˜•์ด ๋ฌผ๋ณด๋‹ค ์ด๋งŒํผ(m ยท ๋†’์ด) ๋†’์€ ๊ณณ๊นŒ์ง€ ๊ฑฐํ’ˆ์ด ์˜ฌ๋ผ์˜จ๋‹ค(์™„๋งŒํ•œ ๊ณณ์€ ๋„“๊ฒŒ ยท ๊ฐ€ํŒŒ๋ฅธ ๊ณณ์€ ์ข๊ฒŒ = ์ž์—ฐ์Šค๋Ÿฌ์šด ๋ฌผ๋ณด๋ผ).")] + public float shoreFoamUp = 0.08f; + + [Tooltip("๋ฐ๋ชจ ํ„ฐ๋ ˆ์ธ ๋ชจ๋“œ์˜ ๋ฌผ๊ฐ€ ๋  โ€” ๋ฌผ๊ฐ€์„ ์—์„œ **๋ฐ”๋‹ค ์ชฝ**์œผ๋กœ ์ˆ˜์‹ฌ ์ด๋งŒํผ(m)๊นŒ์ง€ ๊ฑฐํ’ˆ์ด ํผ์ง„๋‹ค.")] + public float shoreFoamDown = 0.25f; + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ ยง816zf ๋ถ€๋“œ๋Ÿฌ์šด ํ™ ๋ฐ์นผ [Header("ยง816zf โ€” ํ™ ์ž๋ฆฌ๋ฅผ ๋ฐ๋ชจ์ฒ˜๋Ÿผ ใ€Œ๋‘ฅ๊ธ€๊ฒŒ ๋ฒˆ์ง€๊ฒŒใ€ (PD 2026-09-15)")] [Tooltip("0 = ์ง€๊ธˆ(๊ฐ์ง„ 1ร—1 ํ™ ํƒ€์ผ ยท 100 % ๋ณต๊ท€) ยท 1 = ๋ฐญ ๊ตฌ์—ญ์„ ๋ฎ๋Š” **๋ถ€๋“œ๋Ÿฌ์šด ํ™ ๋ฐ์นผ 1์žฅ**" + @@ -402,7 +422,11 @@ namespace WL.Look.Farm [Tooltip("๋ฐ๋ชจ ์ง€ํ˜•ยทํ’€ยท๊ฝƒ ์žฌ์งˆ์˜ ๊ตฌ๋ฆ„ ๊ทธ๋ฆผ์ž ๊ฐ•๋„๋ฅผ ์ด ๊ฐ’์œผ๋กœ ๋ฎ์–ด์“ด ๋Ÿฐํƒ€์ž„ ๋ณต์‚ฌ๋ณธ์„ ์“ด๋‹ค(์›๋ณธ ๋ฌด์ˆ˜์ •). " + "2026-09-16 PD ใ€Œํ’€์ด ์–ผ๋ฃฉ๋œ๋ฃฉใ€ A/B ์‹ค์ธก: ๋ฐ๋ชจ ์›๊ฐ’ 1.0 ์€ 200 m ์ง€ํ˜•์šฉ์ด๋ผ 32 m ์„ฌ์ด ํ†ต์งธ๋กœ ์–ด๋‘์›Œ์กŒ๋‹ค โ†’ 0.3. 0 ์ดํ•˜ = ์›๋ณธ ๊ทธ๋Œ€๋กœ.")] - public float demoCloudStrength = 0.3f; + public float demoCloudStrength = 0.5f; // 2026-09-16 PD ใ€Œ๋ฐ๋ชจ ๊ฐ™์€ ๊ทธ๋ผ๋ฐ์ด์…˜ใ€ A/B: 0.25 ๋Š” ์•ˆ ๋ณด์ด๊ณ  0.5 ๊ฐ€ ๋ฐ๋ชจ์˜ ๊ตฌ๋ฆ„ ๊ทธ๋Š˜ ๋ฌด๋Šฌ(0.7 ์€ ๊ณผํ•จ) + + [Tooltip("๋ฐ๋ชจ ์ง€ํ˜•ยทํ’€ยท๊ฝƒ ์žฌ์งˆ์˜ ๊ตฌ๋ฆ„ ๋ฌด๋Šฌ ๋ฐ€๋„(์›”๋“œ m ๋‹น UV). ๋ฐ๋ชจ ์›๊ฐ’ 0.01 = ๊ตฌ๋ฆ„ ํ•œ ๋ฉ์ด 100 m ๋ผ 32 m ์„ฌ์€ ์ ˆ๋ฐ˜์ด ํ†ต์งธ๋กœ ๋ฎ์ธ๋‹ค โ†’ 0.025(ํ•œ ๋ฉ์ด 40 m ยท ์„ฌ ์œ„์— ๋ฐ์Œ/๊ทธ๋Š˜์ด ํ•œ ๋ฒˆ์”ฉ). " + + "2026-09-16 PD ใ€Œ์„ฌ ํฌ๊ธฐ๋ฅผ ๊ณ ๋ คํ•ด ๊ตฌ๋ฆ„ ๊ทธ๋ฆผ์ž ์กฐ์ ˆใ€. 0 ์ดํ•˜ = ์›๋ณธ ๊ทธ๋Œ€๋กœ.")] + public float demoCloudDensity = 0.025f; [Tooltip("๋†’์ด๋งต ํ•ด์ƒ๋„(2โฟ+1 ยท 33~4097). 129 = ํ•œ ๋ณ€ 128 m ์ผ ๋•Œ 1 m ๊ฐ„๊ฒฉ, ์ž‘์€ ์„ฌ์ด๋ฉด ๋” ์ด˜์ด˜ํ•ด์ง„๋‹ค.")] public int terrainHeightRes = 129; @@ -422,8 +446,53 @@ namespace WL.Look.Farm [Tooltip("๊ฒฝ์‚ฌ ๋ฐ”๊นฅ ๋ = ๋ฐ”๋‹ค ๋ฐ”๋‹ฅ ๋†’์ด(์›”๋“œ y ยท m). ๋ฌผ(โˆ’1.0) ์•„๋ž˜.")] public float terrainBottomY = -1.8f; - [Tooltip("์ž”๋”” ๊ฒฝ๊ณ„์—์„œ ๋ฐ”๋‹ฅ๊นŒ์ง€ ๋‚ด๋ ค๊ฐ€๋Š” ๊ฒฝ์‚ฌ์˜ ํญ(m). smoothstep โ€” ์œ—๋ฉด์—์„œ๋„ ๋ฐ”๋‹ฅ์—์„œ๋„ ์ ‘์„ ์ด ์ˆ˜ํ‰.")] - public float terrainSlope = 3f; + [Tooltip("์ž”๋”” ๊ฒฝ๊ณ„์—์„œ ๋ฐ”๋‹ฅ๊นŒ์ง€ ๋‚ด๋ ค๊ฐ€๋Š” ๊ฒฝ์‚ฌ์˜ ํญ(m). smoothstep โ€” ์œ—๋ฉด์—์„œ๋„ ๋ฐ”๋‹ฅ์—์„œ๋„ ์ ‘์„ ์ด ์ˆ˜ํ‰. " + + "2026-09-16 PD ใ€Œ๊ฐ€์žฅ์ž๋ฆฌ โ†’ ๋ฐ”๋‹ค๋ฅผ ์ž์—ฐ์Šค๋Ÿฌ์šด ๊ฒฝ์‚ฌ๋ฉด์œผ๋กœใ€ โ†’ 3 โ†’ 5(๋ฌผ(โˆ’1)๊นŒ์ง€ ์•ฝ 2.6 m ์˜ ์™„๋งŒํ•œ ํ•ด๋ณ€).")] + public float terrainSlope = 5f; + + // โ”€โ”€ 2026-09-16 PD ใ€Œ์„ฌ ๊ฐ€์žฅ์ž๋ฆฌ์™€ ๋ฐ”๋‹ค๋กœ ์ด์–ด์ง€๋Š” ๋ถ€๋ถ„์„ ์ž์—ฐ์Šค๋Ÿฌ์šด ๊ฒฝ์‚ฌ๋ฉด์œผ๋กœ ยท ๊ตด๊ณก์ด ๋ณด์ด๊ฒŒใ€ + [Tooltip("๊ฒฝ์‚ฌ ํญ์„ ์ž๋ฆฌ๋งˆ๋‹ค ์ด ๋น„์œจ(ยฑ)๋กœ ํ”๋“ ๋‹ค(๊ฐ’ ๋…ธ์ด์ฆˆ ยท ๊ฒฉ์ž terrainSlopeVarCell). 0.35 = ํญ ร—0.65~1.35 โ†’ ํ•ด์•ˆ์ด ๋„“์–ด์กŒ๋‹ค ์ข์•„์กŒ๋‹ค ํ•œ๋‹ค. 0 = ๊ท ์ผ.")] + [Range(0f, 0.8f)] public float terrainSlopeVar = 0.35f; + + [Tooltip("๊ฒฝ์‚ฌ ํญ ํ”๋“ค๊ธฐ ๋…ธ์ด์ฆˆ ๊ฒฉ์ž(m).")] + public float terrainSlopeVarCell = 5f; + + [Tooltip("๊ฒฝ์‚ฌ๋ฉด ์œ„์— ์–น๋Š” ๋Šฅ์„ ยท๊ณจ ๊ตด๊ณก ์ง„ํญ(m ยท ๊ฒฝ์‚ฌ ์–‘ ๋์—์„œ 0 ์ด๋ผ ์œ—๋ฉดยท๋ฐ”๋‹ฅ๊ณผ ์ด์–ด์ง„๋‹ค). ๋ฐ๋ชจ ์ง€ํ˜•์ฒ˜๋Ÿผ ๋ฒ•์„  ์Œ์˜์ด ๊ตด๊ณก์„ ๋“œ๋Ÿฌ๋‚ธ๋‹ค. 0 = ๋งค๋ˆํ•œ smoothstep.")] + public float terrainBumpAmp = 0.25f; + + [Tooltip("๊ตด๊ณก ๋…ธ์ด์ฆˆ ๊ฒฉ์ž(m).")] + public float terrainBumpCell = 2.5f; + + [Tooltip("๊ฒฝ์‚ฌ ๋ฐ– ๋ฐ”๋‹ค ๋ฐ”๋‹ฅ์˜ ์™„๋งŒํ•œ ๊ตด๊ณก ์ง„ํญ(m ยท ๊ฒฝ์‚ฌ ๋์—์„œ 2 m ์— ๊ฑธ์ณ ์„œ์„œํžˆ ์ปค์ง„๋‹ค). ๋ฌผ์ด ๊นŠ์ด๋กœ ์ƒ‰์„ ์ •ํ•˜๋ฏ€๋กœ ์–•์€ ๊ณณ/๊นŠ์€ ๊ณณ ์–ผ๋ฃฉ์œผ๋กœ ๋ณด์ธ๋‹ค. 0 = ํ‰ํ‰.")] + public float terrainSeabedBumpAmp = 0.12f; + + [Tooltip("๋ฐ”๋‹ค ๋ฐ”๋‹ฅ ๊ตด๊ณก ๊ฒฉ์ž(m).")] + public float terrainSeabedBumpCell = 3.5f; + + // โ”€โ”€ 2026-09-16 PD ใ€Œ์ž”๋”” ์–ผ๋ฃฉ์€ ์‚ฌ๋ผ์กŒ์ง€๋งŒ ๋ฐ๋ชจ ๊ฐ™์€ ๊ทธ๋ผ๋ฐ์ด์…˜์ด ์•ˆ ๋ณด์ด๊ณ  ๊ทธ๋ฆผ์ž๊ฐ€ 1๋‹จ๊ณ„๋ฟใ€ โ†’ ์„ฌ ์œ—๋ฉด ๋‘”๋•(๋ฐ๋ชจ ์–ธ๋• ์Œ์˜) + [Tooltip("์„ฌ ์œ—๋ฉด์— ์–น๋Š” ์™„๋งŒํ•œ ๋‘”๋•์˜ ์ตœ๋Œ€ ๋†’์ด(m ยท **์œ„๋กœ๋งŒ** 0~๊ฐ’). ๋ฐ๋ชจ ์ง€ํ˜•์˜ ์–ธ๋•์ฒ˜๋Ÿผ ํ† ์˜จ ๋žจํ”„๊ฐ€ ๋ฐ์Œ/๊ทธ๋Š˜ ๋‹จ๊ณ„๋ฅผ ๋งŒ๋“ ๋‹ค. " + + "์ž”๋”” ๊ฒฝ๊ณ„ ๊ทผ์ฒ˜(terrainReliefEdgeFade) ยท ๋ฐญ ๊ตฌ์—ญ ยท ์„ค์น˜ ์ž๋ฆฌ(ํ”Œ๋กœ์šฐ ๋‹จ๊ณ„ ยท ๋˜์ „ ์ž…๊ตฌ ยท ์บ ํ”„ ์ฒœ๋ง‰) ์ฃผ๋ณ€์€ 0(ํ‰ํ‰). 0 = ํ‰์ง€(์ง€๊ธˆ ๊ตฌ์กฐ).")] + public float terrainReliefAmp = 0.5f; + + [Tooltip("๋‘”๋• ๋…ธ์ด์ฆˆ ๊ฒฉ์ž(m). ์ž‘์„์ˆ˜๋ก ์ž”๋‘”๋•์ด ๋งŽ์•„์ง€๊ณ  ๊ฒฝ์‚ฌ๊ฐ€ ๊ฐ€ํŒ”๋ผ์ง„๋‹ค.")] + public float terrainReliefCell = 4.5f; + + [Tooltip("์ž”๋”” ๊ฒฝ๊ณ„์—์„œ ์•ˆ์ชฝ์œผ๋กœ ์ด๋งŒํผ(m)์— ๊ฑธ์ณ ๋‘”๋•์ด 0 ์—์„œ ์ž๋ž€๋‹ค(ํ•ด๋ณ€ ๊ฒฝ์‚ฌ์™€ ๋งค๋ˆํ•˜๊ฒŒ ์ด์–ด์ง„๋‹ค).")] + public float terrainReliefEdgeFade = 2.5f; + + [Tooltip("์„ค์น˜ ์ž๋ฆฌ(ํ”Œ๋กœ์šฐ ๋‹จ๊ณ„ ์œ„์น˜ ยท ๋˜์ „ ์ž…๊ตฌ) ๋‘˜๋ ˆ ์ด ๋ฐ˜์ง€๋ฆ„(m) ์•ˆ์€ ํ‰ํ‰. ์บ ํ”„ํŒŒ์ด์–ด ์ž๋ฆฌ๋Š” ์ฒœ๋ง‰ ๋ฐ˜์ง€๋ฆ„ + 1.5 m ๋กœ ๋„“ํžŒ๋‹ค.")] + public float terrainReliefFlatRadius = 3f; + + [Tooltip("ํ‰ํ‰ํ•œ ๊ณณ โ†’ ๋‘”๋•์œผ๋กœ ๋„˜์–ด๊ฐ€๋Š” ์™„์ถฉ ํญ(m).")] + public float terrainReliefFeather = 1.5f; + + [Tooltip("1 = ๋ฐœ์ž๊ตญ ์•ˆ(ํƒ€์ผ ๋„ค๋ชจ)์—๋งŒ ๋‘”๋• ๋†’์ด๋ฅผ ๋”ฐ๋ฅด๋Š” MeshCollider ๋ฅผ ๊น๋‹ค โ†’ ํ”Œ๋ ˆ์ด์–ด(CharacterController)๊ฐ€ ๋‘”๋• ์œ„๋ฅผ ๊ฑท๋Š”๋‹ค. " + + "FI ํƒ€์ผ ์ฝœ๋ผ์ด๋”๋Š” ๊ทธ๋Œ€๋กœ(๊ฐ€์žฅ์ž๋ฆฌ ๋™์ž‘ ๋ถˆ๋ณ€). 0 = ์ฝœ๋ผ์ด๋” ์—†์Œ(๋‘”๋•์— ๋ฐœ์ด ๋ฌปํžŒ๋‹ค).")] + public int terrainReliefCollider = 1; + + [Tooltip("๋ฐ๋ชจ ์ง€ํ˜•ยทํ’€ ์žฌ์งˆ์˜ ํ† ์˜จ ๋žจํ”„ ๋ฐ๊ธฐ ์˜คํ”„์…‹(`_Brightness`)์„ ์ด ๊ฐ’์œผ๋กœ ๋ฎ์–ด์“ด ๋Ÿฐํƒ€์ž„ ๋ณต์‚ฌ๋ณธ์„ ์“ด๋‹ค(์›๋ณธ 0.25). " + + "0 ์ดํ•˜ = ์›๋ณธ ๊ทธ๋Œ€๋กœ. ๋‚ฎ์ถœ์ˆ˜๋ก ํ‰์ง€๊ฐ€ ๋žจํ”„ ๋‹จ๊ณ„ ๊ฒฝ๊ณ„ ๊ฐ€๊นŒ์ด ๋‚ด๋ ค์™€ ์ž‘์€ ๊ฒฝ์‚ฌ์—๋„ ๋‹จ๊ณ„๊ฐ€ ๊ฐˆ๋ฆฐ๋‹ค(๋ฐ๋ชจ ์–ธ๋• ์Œ์˜์ด ์ž˜ ๋ณด์ธ๋‹ค).")] + public float demoTerrainBrightness = 0.12f; // 2026-09-16 A/B: 0.25(์›๋ณธ) ๋Š” ๊ตฌ๋ฆ„ ๊ทธ๋Š˜์ด 1๋‹จ๊ณ„ ยท 0.12 ๋ฉด ๊ทธ๋Š˜ ์•ˆ์— 2~3๋‹จ๊ณ„ ๋  + ๋‘”๋• ๊ฒฝ์‚ฌ๋ฉด ์Œ์˜์ด ๋“œ๋Ÿฌ๋‚œ๋‹ค [Tooltip("๋†’์ด๋งต size.y(m) โ€” ๋ฐ”๋‹ฅ(terrainBottomY)์—์„œ ์œ„๋กœ ์ด๋งŒํผ๊นŒ์ง€ ํ‘œํ˜„ํ•œ๋‹ค(๋†’์ด๊ฐ’ 1.0 = ๋ฐ”๋‹ฅ + ์ด ๊ฐ’). ํƒ€์ผ ์œ—๋ฉด(0)์ด ๋“ค์–ด๊ฐ€๋ฉด ๋œ๋‹ค.")] public float terrainHeightSpan = 10f; diff --git a/Assets/WL/Look/Farm/WLShoreFoam.cs b/Assets/WL/Look/Farm/WLShoreFoam.cs index e77d5c4f0..44380b3a4 100644 --- a/Assets/WL/Look/Farm/WLShoreFoam.cs +++ b/Assets/WL/Look/Farm/WLShoreFoam.cs @@ -43,8 +43,11 @@ namespace WL.Look.Farm Mesh _mesh; float _scroll; - void OnEnable() { Ensure(); Rebuild(); } - void OnDisable() { if (_mr != null) _mr.enabled = false; } + void OnEnable() { Ensure(); WLDemoTerrain.Baked += OnTerrainBaked; Rebuild(); } + void OnDisable() { WLDemoTerrain.Baked -= OnTerrainBaked; if (_mr != null) _mr.enabled = false; } + + /// ๋ฐ๋ชจ ํ„ฐ๋ ˆ์ธ์ด ์‹ค์ œ๋กœ ๋‹ค์‹œ ๊ตฌ์›Œ์กŒ์„ ๋•Œ(ํ™•์žฅยท๋ฐญ ๋ณ€ํ™”) ๋ฌผ๊ฐ€์„ ์„ ๋‹ค์‹œ ๋”ด๋‹ค. + void OnTerrainBaked() { if (isActiveAndEnabled) Rebuild(); } void Ensure() { @@ -95,6 +98,17 @@ namespace WL.Look.Farm } _mr.sharedMaterial = _inst; + // ๐Ÿ”ด 2026-09-16 PD ใ€Œ๋ฌผ์ด ์„ฌ ๊ฒฝ๊ณ„์— ๋‹ฟ์„ ๋•Œ ์ž์—ฐ์Šค๋Ÿฌ์šด ๋ฌผ๋ณด๋ผใ€ โ€” ๋ฐ๋ชจ ํ„ฐ๋ ˆ์ธ ๋ชจ๋“œ๋ฉด ํƒ€์ผ ๋ฐœ์ž๊ตญ(๋„ค๋ชจ) ๋Œ€์‹  + // **ํ„ฐ๋ ˆ์ธ ๋ฌผ๊ฐ€์„ **(์ง€ํ˜• ๋†’์ด = ๋ฌผ ๋†’์ด)์„ ๋”ฐ๋ผ ๋ ๋ฅผ ๋‘๋ฅธ๋‹ค. ๊ฒฝ์‚ฌ๊ฐ€ ์™„๋งŒํ•œ ๊ณณ์€ ๋„“๊ณ  ๊ฐ€ํŒŒ๋ฅธ ๊ณณ์€ ์ข์•„ ์ž์—ฐ์Šค๋Ÿฝ๋‹ค. + if (cfg.demoTerrainEnabled != 0) + { + var dt = Object.FindFirstObjectByType(FindObjectsInactive.Include); + var terrain = dt != null ? dt.TerrainComponent : null; + if (terrain == null) { _mesh.Clear(); _mr.enabled = false; LastLog = "ํ„ฐ๋ ˆ์ธ ๋Œ€๊ธฐ"; return; } + BuildFromTerrain(terrain); + return; + } + var tiles = CollectTiles(); Tiles = tiles.Count; if (tiles.Count == 0) { _mesh.Clear(); _mr.enabled = false; LastLog = "ํƒ€์ผ 0"; return; } @@ -184,6 +198,139 @@ namespace WL.Look.Farm } // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + // ๋ฐ๋ชจ ํ„ฐ๋ ˆ์ธ ๋ฌผ๊ฐ€์„  ๋  โ€” ๊ฒฉ์ž๋งˆ๋‹ค f = ๋ฌผ ๋†’์ด โˆ’ ์ง€ํ˜• ๋†’์ด(+ = ๋ฌผ์† ๊นŠ์ด ยท โˆ’ = ๋ชจ๋ž˜ ์œ„ ๋†’์ด). + // ๋  = f โˆˆ [โˆ’shoreFoamUp, +shoreFoamDown] ์— ๊ฑธ์น˜๋Š” ์นธ. ์ •์  ๋†’์ด = max(์ง€ํ˜•, ๋ฌผ) + shoreFoamY โ†’ ๋ชจ๋ž˜ ์œ„์—์„œ๋Š” ๋ชจ๋ž˜๋ฅผ ๋ฎ๊ณ  ๋ฌผ ์œ„์—์„œ๋Š” ๋ฌผ์„ ๋ฎ๋Š”๋‹ค. + // UV v = 0(๋ชจ๋ž˜ ๋) โ†’ 1(๋ฐ”๋‹ค ๋) โ€” ๊ธฐ์กด ๋  ํ…์Šค์ฒ˜์™€ ๊ฐ™์€ ๋ฐฉํ–ฅ. u = ํ•ด์•ˆ ๋ฐฉํ–ฅ ๋ฐ˜๋ณต(shoreFoamTileLength). + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + public static int TerrainNodes; + + void BuildFromTerrain(Terrain terrain) + { + float waterY = WaterYOfScene(terrain.gameObject.scene); + float up = Mathf.Max(0.01f, cfg.shoreFoamUp), down = Mathf.Max(0.01f, cfg.shoreFoamDown); + float cell = Mathf.Max(0.15f, cellOverride > 0f ? cellOverride : cfg.shoreFoamCell); + float lift = cfg.shoreFoamY; + float tileLen = Mathf.Max(0.5f, cfg.shoreFoamTileLength); + var tp = terrain.transform.position; + var sz = terrain.terrainData.size; + // ๋  ํ…์Šค์ฒ˜ = ๋Ÿฐํƒ€์ž„ ์ƒ์„ฑ(๋ฌผ๊ฐ€์„ ์—์„œ ์ง„ํ•˜๊ณ  ๋ชจ๋ž˜ ์ชฝ์€ ์งง๊ฒŒ ยท ๋ฐ”๋‹ค ์ชฝ์€ ๊ธธ๊ฒŒ ๋“ค์ญ‰๋‚ ์ญ‰ ์‚ฌ๋ผ์ง„๋‹ค) โ€” ์—์…‹ ํ…์Šค์ฒ˜๋Š” ๋„ค๋ชจ ๋ (816j2)์šฉ์ด๋ผ ๊ฐ€์žฅ์ž๋ฆฌ๊ฐ€ ๋”ฑ ๋Š๊ธด๋‹ค + var tex = FoamTexture(up / (up + down)); + _inst.SetTexture(IdBase, tex); + if (_inst.HasProperty("_MainTex")) _inst.SetTexture("_MainTex", tex); + int nx = Mathf.Clamp(Mathf.CeilToInt(sz.x / cell) + 1, 2, 1024); + int nz = Mathf.Clamp(Mathf.CeilToInt(sz.z / cell) + 1, 2, 1024); + float stepX = sz.x / (nx - 1), stepZ = sz.z / (nz - 1); + + var hgt = new float[nx * nz]; + var f = new float[nx * nz]; + var probe = new Vector3(); + for (int j = 0; j < nz; j++) + { + probe.z = tp.z + j * stepZ; + for (int i = 0; i < nx; i++) + { + probe.x = tp.x + i * stepX; + float h = terrain.SampleHeight(probe) + tp.y; + int k = j * nx + i; + hgt[k] = h; f[k] = waterY - h; + } + } + TerrainNodes = nx * nz; + + var vmap = new int[nx * nz]; + for (int k = 0; k < vmap.Length; k++) vmap[k] = -1; + var verts = new List(2048); + var uvs = new List(2048); + var tris = new List(4096); + + for (int j = 0; j < nz - 1; j++) + for (int i = 0; i < nx - 1; i++) + { + int a = j * nx + i, b = a + 1, c = a + nx, dd = c + 1; + float lo = Mathf.Min(Mathf.Min(f[a], f[b]), Mathf.Min(f[c], f[dd])); + float hi = Mathf.Max(Mathf.Max(f[a], f[b]), Mathf.Max(f[c], f[dd])); + if (lo > down || hi < -up) continue; + int va = V(a), vb = V(b), vc = V(c), vd = V(dd); + tris.Add(va); tris.Add(vc); tris.Add(vb); + tris.Add(vb); tris.Add(vc); tris.Add(vd); + } + + int V(int k) + { + if (vmap[k] >= 0) return vmap[k]; + int i = k % nx, j = k / nx; + float x = tp.x + i * stepX, z = tp.z + j * stepZ; + verts.Add(new Vector3(x, Mathf.Max(hgt[k], waterY) + lift, z)); + float v = Mathf.Clamp01((f[k] + up) / (down + up)); + uvs.Add(new Vector2((x * 0.7071f + z * 0.7071f) / tileLen, v)); + vmap[k] = verts.Count - 1; + return vmap[k]; + } + + _mesh.Clear(); + if (verts.Count == 0 || tris.Count == 0) { _mr.enabled = false; LastLog = "๋ฌผ๊ฐ€์„  ๋  0(๋ฌผ ๋†’์ด " + waterY.ToString("F2") + ")"; return; } + _mesh.indexFormat = verts.Count > 65000 + ? UnityEngine.Rendering.IndexFormat.UInt32 : UnityEngine.Rendering.IndexFormat.UInt16; + _mesh.SetVertices(verts); + _mesh.SetUVs(0, uvs); + _mesh.SetTriangles(tris, 0); + _mesh.RecalculateBounds(); + _mr.enabled = true; + + Rebuilds++; Verts = verts.Count; Tris = tris.Count / 3; Tiles = 0; + LastLog = "ํ„ฐ๋ ˆ์ธ ๋ฌผ๊ฐ€์„  ๋  โ€” ์‚ผ๊ฐํ˜• " + Tris + " ยท ์ •์  " + Verts + " ยท ๋ฌผ y " + waterY.ToString("F2") + + " ยท ๋ชจ๋ž˜ ์ชฝ " + up.ToString("F2") + " m โ†‘ ยท ๋ฐ”๋‹ค ์ชฝ ์ˆ˜์‹ฌ " + down.ToString("F2") + " m ยท ๊ฒฉ์ž " + cell.ToString("F2") + " m ยท ๋“œ๋กœ์šฐ์ฝœ +1"; + cfg.Log("๋‘˜๋ ˆ ๊ฑฐํ’ˆ ๋  โ€” " + LastLog); + } + + static float SmoothStep01(float t) { t = Mathf.Clamp01(t); return t * t * (3f - 2f * t); } + + // ๋ฌผ๊ฐ€์„  ๋  ํ…์Šค์ฒ˜(๋Ÿฐํƒ€์ž„ ยท ์—์…‹ 0์ค„): u = ํ•ด์•ˆ ๋ฐฉํ–ฅ(์ฃผ๊ธฐ 1 ยท ๋ฐ˜๋ณต) ยท v = ๋ชจ๋ž˜ ๋(0) โ†’ ๋ฌผ๊ฐ€์„ (vWl) โ†’ ๋ฐ”๋‹ค ๋(1). + // ์•ŒํŒŒ = ๋ฌผ๊ฐ€์„  1 ยท ๋ชจ๋ž˜ ์ชฝ์œผ๋กœ ์งง๊ฒŒ(์ œ๊ณฑ) ยท ๋ฐ”๋‹ค ์ชฝ์œผ๋กœ ๊ธธ๊ฒŒ, ๋ฐ”๋‹ค ์ชฝ ๋์€ ํ•ด์•ˆ ๋ฐฉํ–ฅ ํŒŒํ˜•์œผ๋กœ ๋“ค์ญ‰๋‚ ์ญ‰. + static Texture2D s_foamTex; + static float s_foamTexKey = float.NaN; + static Texture2D FoamTexture(float vWl) + { + vWl = Mathf.Clamp(vWl, 0.05f, 0.95f); + if (s_foamTex != null && Mathf.Abs(s_foamTexKey - vWl) < 0.001f) return s_foamTex; + const int W = 256, H = 64; + if (s_foamTex == null) + s_foamTex = new Texture2D(W, H, TextureFormat.RGBA32, false) + { name = "WL_ShoreFoam(runtime)", hideFlags = HideFlags.DontSave, wrapMode = TextureWrapMode.Repeat, filterMode = FilterMode.Bilinear }; + var px = new Color32[W * H]; + for (int y = 0; y < H; y++) + { + float v = (y + 0.5f) / H; + float a = v < vWl ? Mathf.Pow(Mathf.Clamp01(v / vWl), 2f) : 1f - SmoothStep01((v - vWl) / (1f - vWl)); + for (int x = 0; x < W; x++) + { + float u = (float)x / W; + float n = 0.5f + 0.5f * Mathf.Sin(u * 6.2832f * 3f + Mathf.Sin(u * 6.2832f * 7f + v * 3f) * 1.3f); + float edge = v > vWl ? Mathf.Clamp01(1f - (v - vWl) / (1f - vWl) * (0.6f + 0.8f * n)) : 1f; + float alpha = Mathf.Clamp01(a * edge * (0.75f + 0.25f * n)); + px[y * W + x] = new Color32(255, 255, 255, (byte)(alpha * 255f)); + } + } + s_foamTex.SetPixels32(px); + s_foamTex.Apply(false, false); + s_foamTexKey = vWl; + return s_foamTex; + } + + /// ๋ฌผ ํ‰๋ฉด์˜ ๋†’์ด(์”ฌ ๊ธฐ์ค€) โ€” `Water` ๋ฃจํŠธ/์ž์‹์„ ์ฐพ๊ณ  ์—†์œผ๋ฉด ์„ค์ •๊ฐ’. + float WaterYOfScene(UnityEngine.SceneManagement.Scene scene) + { + if (!scene.IsValid()) return cfg.shoreFoamFallbackY; + var roots = scene.GetRootGameObjects(); + for (int i = 0; i < roots.Length; i++) + { + if (roots[i].name == "Water") return roots[i].transform.position.y; + var t = roots[i].transform.Find("Water"); + if (t != null) return t.position.y; + } + return cfg.shoreFoamFallbackY; + } + struct TileRef { public Transform tf; public Vector3 center; public WLTileMask mask; public float half; } readonly List _tiles = new List(96); diff --git a/Assets/WL/Look/Farm/WLSkyScope.cs b/Assets/WL/Look/Farm/WLSkyScope.cs index eabb16837..49eef2422 100644 --- a/Assets/WL/Look/Farm/WLSkyScope.cs +++ b/Assets/WL/Look/Farm/WLSkyScope.cs @@ -70,6 +70,10 @@ namespace WL.Look.Farm SceneManager.sceneLoaded += OnSceneLoaded; SceneManager.sceneUnloaded += OnSceneUnloaded; SceneManager.activeSceneChanged += OnActiveSceneChanged; +#if UNITY_EDITOR + // ๐Ÿ”ด SSAO ๋Š” ๋ Œ๋”๋Ÿฌ **์—์…‹**์˜ ๊ฐ’์ด๋ผ ์„ฌ์—์„œ Play ๋ฅผ ๋„๋ฉด ใ€Œ๊บผ์ง„ ์ฑ„ใ€ ๋ฉ”๋ชจ๋ฆฌ์— ๋‚จ๋Š”๋‹ค โ†’ Play ๋ฅผ ๋‚˜๊ฐˆ ๋•Œ ๋ฐ˜๋“œ์‹œ ๋˜๋Œ๋ฆฐ๋‹ค. + UnityEditor.EditorApplication.playModeStateChanged += st => { if (st == UnityEditor.PlayModeStateChange.ExitingPlayMode) RestoreSsao(); }; +#endif // ๐Ÿ”ด ์•„์ง ์•„๋ฌด ๋งต๋„ ์•ˆ ๋ถ™์€ ์ง€๊ธˆ์ด ใ€Œํ™œ์„ฑ ์”ฌ์˜ ์›๋ž˜ ํ•˜๋Š˜ใ€์ด๋‹ค โ€” ์—ฌ๊ธฐ์„œ ํ•œ ๋ฒˆ๋งŒ ๋œฌ๋‹ค. CaptureIfNeeded(); @@ -77,7 +81,9 @@ namespace WL.Look.Farm for (int i = 0; i < SceneManager.sceneCount; i++) { var sc = SceneManager.GetSceneAt(i); - if (sc.isLoaded && IsScoped(cfg, sc.name)) Acquire(cfg, sc.name); + if (!sc.isLoaded) continue; + if (IsSsaoScoped(cfg, sc.name)) AcquireSsao(cfg, sc.name); + if (IsScoped(cfg, sc.name)) Acquire(cfg, sc.name); } } @@ -101,6 +107,7 @@ namespace WL.Look.Farm { var cfg = WLIslandLookSettings.Instance; if (cfg == null || cfg.enabled_ == 0 || cfg.skyScopeEnabled == 0) return; + if (IsSsaoScoped(cfg, scene.name)) AcquireSsao(cfg, scene.name); if (!IsScoped(cfg, scene.name)) return; Acquire(cfg, scene.name); } @@ -109,7 +116,7 @@ namespace WL.Look.Farm { var cfg = WLIslandLookSettings.Instance; if (cfg == null || cfg.skyScopeEnabled == 0) return; - if (!Held) return; + if (!Held && !SsaoHeld) return; // ๐Ÿ”ด ๋งต์„ ๋ฐ”๊ฟ€ ๋•Œ `SceneInfo` ๋Š” **๊ฐ™์€ ํ”„๋ ˆ์ž„์—** ๋‚ด๋ฆฌ๊ธฐ์™€ ์˜ฌ๋ฆฌ๊ธฐ๋ฅผ ํ•จ๊ป˜ ์‹œ์ž‘ํ•œ๋‹ค. // ๊ทธ๋ž˜์„œ ๋ฐ”๋กœ ๋˜๋Œ๋ฆฌ๋ฉด ใ€Œ์„ฌ โ†’ ๋˜์ „ใ€ ์—์„œ ์ž˜๋ชป ๋˜๋Œ์•„๊ฐ„๋‹ค. ๋‘ ํ”„๋ ˆ์ž„ ๊ธฐ๋‹ค๋ ธ๋‹ค // **๊ทธ๋•Œ๋„ ๋ฒ”์œ„ ๋งต์ด ํ•˜๋‚˜๋„ ์—†์„ ๋•Œ๋งŒ** ๋˜๋Œ๋ฆฐ๋‹ค. @@ -131,12 +138,64 @@ namespace WL.Look.Farm static void ReleaseIfEmpty(WLIslandLookSettings cfg) { + bool sky = false, ssao = false; for (int i = 0; i < SceneManager.sceneCount; i++) { var sc = SceneManager.GetSceneAt(i); - if (sc.isLoaded && IsScoped(cfg, sc.name)) return; // ์•„์ง ์„ฌ/๋˜์ „์— ์žˆ๋‹ค + if (!sc.isLoaded) continue; + if (IsScoped(cfg, sc.name)) sky = true; // ์•„์ง ์„ฌ/๋˜์ „์— ์žˆ๋‹ค + if (IsSsaoScoped(cfg, sc.name)) ssao = true; // ์•„์ง ์„ฌ์— ์žˆ๋‹ค } - Release(cfg); + if (!ssao) RestoreSsao(); + if (!sky) Release(cfg); + } + + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + // ๐Ÿ”ด 2026-09-16 PD ใ€Œํ’€์ด ์–ผ๋ฃฉ๋œ๋ฃฉ โ€” ์›์ธ์„ ๋” ์ฐพ์•„๋ผใ€ A/B ์‹ค์ธก(๊ฐ™์€ ์นด๋ฉ”๋ผ ยท ํ’€๋ฐญ 450ร—290 px): + // SSAO(๋ Œ๋”๋Ÿฌ ํ”ผ์ฒ˜ ยท ๋ฐ˜์ง€๋ฆ„ 0.25 m) ๊ฐ€ ํ’€์žŽ 6๋งŒ ์žฅ๋งˆ๋‹ค ๋ฐœ๋ฐ‘ ๊ทธ๋Š˜์„ ๋งŒ๋“ค์–ด ์–ผ๋ฃฉ์ด ๋๋‹ค โ€” ๋„๋ฉด ๊ตญ์†Œ ์งˆ๊ฐ 0.70 โ†’ 0.07(1/10), + // ๊ตฌ๋ฆ„ ๊ฐ•๋„ 0/0.15 ยท ํ’€์žŽ ์„ธ๋กœ ๋ช…์•” 0 ยท ์นด๋ฉ”๋ผ ๊ฐ๋„ 35/65 ๋Š” ์ „๋ถ€ ยฑ0.05 ์•ˆ(์›์ธ ์•„๋‹˜). + // ๋ Œ๋”๋Ÿฌ ์—์…‹์€ ์ „ํˆฌ ๋งต๊ณผ ๊ณต์œ ๋ผ(ํŒŒ์ผ 0์ค„) **์„ฌ ์”ฌ์ด ๋–  ์žˆ๋Š” ๋™์•ˆ๋งŒ** ํ”ผ์ฒ˜๋ฅผ ๋„๊ณ  ๋‚˜๊ฐˆ ๋•Œ ์ผ ๋‹ค. + // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + public static bool SsaoHeld; + public static int SsaoOffCount, SsaoRestores; + static readonly System.Collections.Generic.List s_ssaoOff = + new System.Collections.Generic.List(2); + + public static bool IsSsaoScoped(WLIslandLookSettings cfg, string sceneName) + { + if (cfg == null || cfg.islandSsaoOff == 0 || cfg.ssaoOffScenes == null) return false; + for (int i = 0; i < cfg.ssaoOffScenes.Length; i++) + if (cfg.ssaoOffScenes[i] == sceneName) return true; + return false; + } + + /// ์„ฌ์— ๋“ค์–ด๊ฐˆ ๋•Œ โ€” ์ผœ์ ธ ์žˆ๋Š” SSAO ํ”ผ์ฒ˜(์ด๋ฆ„ ยท ํ˜•์‹๋ช…์œผ๋กœ ํŒ๋ณ„ ยท URP ํด๋ž˜์Šค๋Š” internal)๋ฅผ ๋„๊ณ  ๊ธฐ์–ตํ•œ๋‹ค. + public static void AcquireSsao(WLIslandLookSettings cfg, string sceneName) + { + if (cfg == null || cfg.islandSsaoOff == 0) return; + var feats = Resources.FindObjectsOfTypeAll(); + for (int i = 0; i < feats.Length; i++) + { + var f = feats[i]; + if (f == null || !f.isActive) continue; + if (f.GetType().Name != "ScreenSpaceAmbientOcclusion" && f.name != "SSAO") continue; + if (s_ssaoOff.Contains(f)) continue; + f.SetActive(false); + s_ssaoOff.Add(f); + SsaoOffCount++; + } + SsaoHeld = s_ssaoOff.Count > 0; + if (SsaoHeld) cfg.Log("SSAO ๋” โ€” " + sceneName + " ๋™์•ˆ ยท ํ”ผ์ฒ˜ " + s_ssaoOff.Count + "๊ฐœ(๋‚˜๊ฐˆ ๋•Œ ๋ณต์›)"); + } + + /// ์„ฌ์„ ๋‚˜๊ฐˆ ๋•Œ(๋˜๋Š” ์—๋””ํ„ฐ Play ์ข…๋ฃŒ) โ€” ์šฐ๋ฆฌ๊ฐ€ ๋ˆ ํ”ผ์ฒ˜๋งŒ ๋‹ค์‹œ ์ผ ๋‹ค. + public static void RestoreSsao() + { + if (s_ssaoOff.Count == 0) { SsaoHeld = false; return; } + for (int i = 0; i < s_ssaoOff.Count; i++) if (s_ssaoOff[i] != null) s_ssaoOff[i].SetActive(true); + SsaoRestores += s_ssaoOff.Count; + s_ssaoOff.Clear(); + SsaoHeld = false; } // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€