350 lines
14 KiB
C#
350 lines
14 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using NiloToon.NiloToonURP;
|
|
using UnityEngine;
|
|
|
|
namespace Streamingle.Background
|
|
{
|
|
/// <summary>
|
|
/// 시간 흐름에 따라 스카이박스 블렌딩 + 디렉셔널 라이트를 제어하는 컨트롤러.
|
|
/// 3단계(Stage1→Stage2→Stage3) 전환을 timeOfDay(0~1)로 통합 제어.
|
|
/// [ExecuteAlways]로 에디터에서도 실시간 프리뷰 가능.
|
|
/// </summary>
|
|
[ExecuteAlways]
|
|
public class SkyboxTimeController : MonoBehaviour
|
|
{
|
|
// ───────────────────────── References ─────────────────────────
|
|
[Tooltip("제어할 디렉셔널 라이트")]
|
|
public Light directionalLight;
|
|
|
|
[Tooltip("Skybox/Cubemap Blend 머티리얼")]
|
|
public Material skyboxMaterial;
|
|
|
|
// ───────────────────────── Time ─────────────────────────
|
|
[Range(0f, 1f)]
|
|
[Tooltip("0=Stage1, 0.5=Stage2, 1=Stage3")]
|
|
public float timeOfDay = 0f;
|
|
|
|
[Tooltip("자동 재생 여부")]
|
|
public bool autoPlay = false;
|
|
|
|
[Tooltip("한 사이클(0→1) 소요 시간(초)")]
|
|
public float cycleDuration = 60f;
|
|
|
|
[Tooltip("자동 재생 시 루프 여부")]
|
|
public bool loop = true;
|
|
|
|
// ───────────────────────── Stage Settings ─────────────────────────
|
|
public StageSettings stage1 = new StageSettings
|
|
{
|
|
lightColor = new Color(0.4f, 0.45f, 0.65f),
|
|
lightIntensity = 0.3f,
|
|
lightRotation = new Vector3(10f, -30f, 0f),
|
|
skyboxExposure = 0.5f,
|
|
skyboxTint = Color.white,
|
|
};
|
|
|
|
public StageSettings stage2 = new StageSettings
|
|
{
|
|
lightColor = new Color(1f, 0.95f, 0.85f),
|
|
lightIntensity = 1.2f,
|
|
lightRotation = new Vector3(50f, -30f, 0f),
|
|
skyboxExposure = 1.0f,
|
|
skyboxTint = Color.white,
|
|
};
|
|
|
|
public StageSettings stage3 = new StageSettings
|
|
{
|
|
lightColor = new Color(1f, 0.55f, 0.3f),
|
|
lightIntensity = 0.6f,
|
|
lightRotation = new Vector3(5f, 150f, 0f),
|
|
skyboxExposure = 0.7f,
|
|
skyboxTint = new Color(1f, 0.85f, 0.75f),
|
|
};
|
|
|
|
// ───────────────────────── Custom Material Properties ─────────────────────────
|
|
[Tooltip("머티리얼의 임의 프로퍼티를 3단계로 보간 제어")]
|
|
public List<MaterialPropertyOverride> materialPropertyOverrides = new List<MaterialPropertyOverride>();
|
|
|
|
// ───────────────────────── NiloToon Light Override ─────────────────────────
|
|
[Tooltip("NiloToon 캐릭터 메인 라이트 오버라이더 (디렉셔널 라이트와 동기화)")]
|
|
public NiloToonCharacterMainLightOverrider niloToonLightOverrider;
|
|
|
|
[Tooltip("NiloToon 라이트 컬러를 별도로 지정할지 여부 (false면 디렉셔널 라이트와 동일)")]
|
|
public bool niloToonSeparateColor = false;
|
|
public Color niloToonColorStage1 = Color.white;
|
|
public Color niloToonColorStage2 = Color.white;
|
|
public Color niloToonColorStage3 = Color.white;
|
|
|
|
[Tooltip("NiloToon 라이트 Intensity를 별도로 지정할지 여부 (false면 디렉셔널 라이트와 동일)")]
|
|
public bool niloToonSeparateIntensity = false;
|
|
public float niloToonIntensityStage1 = 1f;
|
|
public float niloToonIntensityStage2 = 1f;
|
|
public float niloToonIntensityStage3 = 1f;
|
|
|
|
// ───────────────────────── Ambient ─────────────────────────
|
|
[Tooltip("Ambient 컬러도 함께 보간")]
|
|
public bool controlAmbient = true;
|
|
public Color ambientStage1 = new Color(0.15f, 0.15f, 0.25f);
|
|
public Color ambientStage2 = new Color(0.45f, 0.45f, 0.45f);
|
|
public Color ambientStage3 = new Color(0.3f, 0.2f, 0.15f);
|
|
|
|
// ───────────────────────── Private ─────────────────────────
|
|
private Material _instanceMaterial;
|
|
|
|
private static readonly int BlendProp = Shader.PropertyToID("_Blend");
|
|
private static readonly int TintProp = Shader.PropertyToID("_Tint");
|
|
private static readonly int ExposureProp = Shader.PropertyToID("_Exposure");
|
|
|
|
// ───────────────────────── Lifecycle ─────────────────────────
|
|
|
|
private void OnEnable()
|
|
{
|
|
SetupMaterial();
|
|
}
|
|
|
|
private void OnDisable()
|
|
{
|
|
CleanupMaterial();
|
|
}
|
|
|
|
private void Update()
|
|
{
|
|
if (autoPlay && Application.isPlaying && cycleDuration > 0f)
|
|
{
|
|
timeOfDay += Time.deltaTime / cycleDuration;
|
|
if (loop)
|
|
timeOfDay %= 1f;
|
|
else
|
|
timeOfDay = Mathf.Clamp01(timeOfDay);
|
|
}
|
|
|
|
Apply();
|
|
}
|
|
|
|
private void OnValidate()
|
|
{
|
|
SetupMaterial();
|
|
Apply();
|
|
}
|
|
|
|
// ───────────────────────── Core ─────────────────────────
|
|
|
|
public void Apply()
|
|
{
|
|
float t = Mathf.Clamp01(timeOfDay);
|
|
StageSettings current = EvaluateStage(t);
|
|
|
|
// ── Skybox ──
|
|
Material mat = GetActiveMaterial();
|
|
if (mat != null)
|
|
{
|
|
mat.SetFloat(BlendProp, t);
|
|
mat.SetColor(TintProp, current.skyboxTint);
|
|
mat.SetFloat(ExposureProp, current.skyboxExposure);
|
|
|
|
}
|
|
|
|
// ── Custom Material Properties ──
|
|
foreach (var prop in materialPropertyOverrides)
|
|
{
|
|
if (prop.targetMaterial == null || string.IsNullOrEmpty(prop.propertyName)) continue;
|
|
if (!prop.targetMaterial.HasProperty(prop.propertyName)) continue;
|
|
int id = Shader.PropertyToID(prop.propertyName);
|
|
|
|
switch (prop.propertyType)
|
|
{
|
|
case MaterialPropertyType.Float:
|
|
{
|
|
float v = t < 0.5f
|
|
? Mathf.Lerp(prop.floatStage1, prop.floatStage2, t * 2f)
|
|
: Mathf.Lerp(prop.floatStage2, prop.floatStage3, (t - 0.5f) * 2f);
|
|
prop.targetMaterial.SetFloat(id, v);
|
|
break;
|
|
}
|
|
case MaterialPropertyType.Color:
|
|
{
|
|
Color c = t < 0.5f
|
|
? Color.Lerp(prop.colorStage1, prop.colorStage2, t * 2f)
|
|
: Color.Lerp(prop.colorStage2, prop.colorStage3, (t - 0.5f) * 2f);
|
|
prop.targetMaterial.SetColor(id, c);
|
|
break;
|
|
}
|
|
case MaterialPropertyType.Vector:
|
|
{
|
|
Vector4 v = t < 0.5f
|
|
? Vector4.Lerp(prop.vectorStage1, prop.vectorStage2, t * 2f)
|
|
: Vector4.Lerp(prop.vectorStage2, prop.vectorStage3, (t - 0.5f) * 2f);
|
|
prop.targetMaterial.SetVector(id, v);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// ── Directional Light ──
|
|
if (directionalLight != null)
|
|
{
|
|
directionalLight.color = current.lightColor;
|
|
directionalLight.intensity = current.lightIntensity;
|
|
directionalLight.transform.rotation = Quaternion.Euler(current.lightRotation);
|
|
}
|
|
|
|
// ── NiloToon Light Override ──
|
|
if (niloToonLightOverrider != null)
|
|
{
|
|
// Direction: 디렉셔널 라이트와 동일한 rotation 적용
|
|
niloToonLightOverrider.transform.rotation = Quaternion.Euler(current.lightRotation);
|
|
|
|
// Color
|
|
if (niloToonSeparateColor)
|
|
{
|
|
niloToonLightOverrider.color = t < 0.5f
|
|
? Color.Lerp(niloToonColorStage1, niloToonColorStage2, t * 2f)
|
|
: Color.Lerp(niloToonColorStage2, niloToonColorStage3, (t - 0.5f) * 2f);
|
|
}
|
|
else
|
|
{
|
|
niloToonLightOverrider.color = current.lightColor;
|
|
}
|
|
|
|
// Intensity
|
|
if (niloToonSeparateIntensity)
|
|
{
|
|
niloToonLightOverrider.intensity = t < 0.5f
|
|
? Mathf.Lerp(niloToonIntensityStage1, niloToonIntensityStage2, t * 2f)
|
|
: Mathf.Lerp(niloToonIntensityStage2, niloToonIntensityStage3, (t - 0.5f) * 2f);
|
|
}
|
|
else
|
|
{
|
|
niloToonLightOverrider.intensity = current.lightIntensity;
|
|
}
|
|
}
|
|
|
|
// ── Ambient ──
|
|
if (controlAmbient)
|
|
{
|
|
Color amb = t < 0.5f
|
|
? Color.Lerp(ambientStage1, ambientStage2, t * 2f)
|
|
: Color.Lerp(ambientStage2, ambientStage3, (t - 0.5f) * 2f);
|
|
RenderSettings.ambientLight = amb;
|
|
}
|
|
}
|
|
|
|
public void SetTime(float t)
|
|
{
|
|
timeOfDay = Mathf.Clamp01(t);
|
|
Apply();
|
|
}
|
|
|
|
public Material GetActiveMaterial()
|
|
{
|
|
return _instanceMaterial != null ? _instanceMaterial : skyboxMaterial;
|
|
}
|
|
|
|
// ───────────────────────── Helpers ─────────────────────────
|
|
|
|
private StageSettings EvaluateStage(float t)
|
|
{
|
|
if (t < 0.5f)
|
|
return StageSettings.Lerp(stage1, stage2, t * 2f);
|
|
else
|
|
return StageSettings.Lerp(stage2, stage3, (t - 0.5f) * 2f);
|
|
}
|
|
|
|
private void SetupMaterial()
|
|
{
|
|
if (skyboxMaterial == null) return;
|
|
|
|
if (Application.isPlaying)
|
|
{
|
|
if (_instanceMaterial == null || _instanceMaterial.shader != skyboxMaterial.shader)
|
|
{
|
|
CleanupMaterial();
|
|
_instanceMaterial = new Material(skyboxMaterial);
|
|
_instanceMaterial.name = skyboxMaterial.name + " (Instance)";
|
|
}
|
|
}
|
|
else
|
|
{
|
|
_instanceMaterial = skyboxMaterial;
|
|
}
|
|
|
|
RenderSettings.skybox = _instanceMaterial;
|
|
}
|
|
|
|
private void CleanupMaterial()
|
|
{
|
|
if (_instanceMaterial != null && _instanceMaterial != skyboxMaterial)
|
|
{
|
|
if (Application.isPlaying)
|
|
Destroy(_instanceMaterial);
|
|
else
|
|
DestroyImmediate(_instanceMaterial);
|
|
}
|
|
_instanceMaterial = null;
|
|
}
|
|
|
|
// ───────────────────────── Data Structures ─────────────────────────
|
|
|
|
[Serializable]
|
|
public struct StageSettings
|
|
{
|
|
public Color lightColor;
|
|
public float lightIntensity;
|
|
public Vector3 lightRotation;
|
|
public float skyboxExposure;
|
|
public Color skyboxTint;
|
|
|
|
public static StageSettings Lerp(StageSettings a, StageSettings b, float t)
|
|
{
|
|
return new StageSettings
|
|
{
|
|
lightColor = Color.Lerp(a.lightColor, b.lightColor, t),
|
|
lightIntensity = Mathf.Lerp(a.lightIntensity, b.lightIntensity, t),
|
|
lightRotation = LerpRotation(a.lightRotation, b.lightRotation, t),
|
|
skyboxExposure = Mathf.Lerp(a.skyboxExposure, b.skyboxExposure, t),
|
|
skyboxTint = Color.Lerp(a.skyboxTint, b.skyboxTint, t),
|
|
};
|
|
}
|
|
|
|
private static Vector3 LerpRotation(Vector3 a, Vector3 b, float t)
|
|
{
|
|
return Quaternion.Slerp(Quaternion.Euler(a), Quaternion.Euler(b), t).eulerAngles;
|
|
}
|
|
}
|
|
|
|
public enum MaterialPropertyType
|
|
{
|
|
Float,
|
|
Color,
|
|
Vector,
|
|
}
|
|
|
|
[Serializable]
|
|
public class MaterialPropertyOverride
|
|
{
|
|
[Tooltip("제어할 머티리얼")]
|
|
public Material targetMaterial;
|
|
|
|
[Tooltip("셰이더 프로퍼티 이름 (예: _Metallic, _Color)")]
|
|
public string propertyName;
|
|
public MaterialPropertyType propertyType = MaterialPropertyType.Float;
|
|
|
|
// Float
|
|
public float floatStage1;
|
|
public float floatStage2;
|
|
public float floatStage3;
|
|
|
|
// Color
|
|
public Color colorStage1 = Color.white;
|
|
public Color colorStage2 = Color.white;
|
|
public Color colorStage3 = Color.white;
|
|
|
|
// Vector
|
|
public Vector4 vectorStage1;
|
|
public Vector4 vectorStage2;
|
|
public Vector4 vectorStage3;
|
|
}
|
|
}
|
|
}
|