diff --git a/Assets/Scripts/Components/SkyboxEnvironmentUpdater.cs b/Assets/Scripts/Components/SkyboxEnvironmentUpdater.cs new file mode 100644 index 0000000..d743164 --- /dev/null +++ b/Assets/Scripts/Components/SkyboxEnvironmentUpdater.cs @@ -0,0 +1,164 @@ +using System.Collections; +using UnityEngine; +using UnityEngine.Rendering; +using UnityEngine.Rendering.Universal; + +/// +/// Builds the environment reflection from the current Skybox in memory (renders +/// the sky into a cubemap and assigns it as the Custom reflection), so +/// smooth/metallic materials reflect the sky WITHOUT a baked LightingData.asset. +/// Runs in Edit and Play mode ([ExecuteAlways]); drop it on one object per scene +/// (or a prefab that lives in every scene) and you never have to assign a foreign +/// LightingData again — also works for runtime-loaded / procedural scenes. +/// +/// Why a rendered cubemap and not : +/// in this project UpdateEnvironment does NOT produce a valid skybox reflection +/// probe (a mirror stays black), so +/// had nothing to scale. Rendering the sky into a Custom reflection cubemap gives a +/// real reflection that intensity can actually drive. +/// +/// The skybox AMBIENT probe still comes from UpdateEnvironment — but it only convolves a +/// non-zero probe once the sky has actually been rendered this session. Straight after a +/// domain reload / scene load / play-start the sky hasn't rendered yet, so the probe is +/// ZERO and the Environment intensity multiplier has nothing to scale ("doesn't take +/// effect" until a manual re-enable). Fix: do one synchronous full-screen sky render to +/// "warm" the sky right before UpdateEnvironment — deterministic, no waiting on renders. +/// (RenderToCubemap does NOT warm the ambient path; a normal Camera.Render does.) +/// +[ExecuteAlways] +[DisallowMultipleComponent] +public class SkyboxEnvironmentUpdater : MonoBehaviour +{ + [Range(0f, 4f)] + public float environmentLightingIntensity = 1; + + [Header("Reflections")] + + [Tooltip("Resolution of the generated sky reflection cubemap (power of two).")] + public int reflectionResolution = 128; + + [Tooltip("How strongly the sky reflection tints reflective/metallic surfaces. 1 = neutral; the Lighting window slider caps at 1, but higher values (via this field) over-drive the reflection for a glossier metal look. Only applied while Force Skybox Reflections is on.")] + [Range(0f, 4f)] + public float reflectionIntensity = 1f; + + [Tooltip("Optional: render THIS sky material into the reflection cubemap instead of the scene's global Skybox. Leave empty to use RenderSettings.skybox. Only affects the reflection — the visible scene skybox is unchanged.")] + public Material reflectionSkyOverride; + + [System.NonSerialized] Cubemap _reflectionCube; + + void OnDisable() + { +#if UNITY_EDITOR + UnityEditor.EditorApplication.delayCall -= Apply; +#endif + if (!_reflectionCube) + return; + + if (RenderSettings.customReflectionTexture == _reflectionCube) + RenderSettings.customReflectionTexture = null; + + DestroyImmediate(_reflectionCube); + + _reflectionCube = null; + } + + void OnValidate() + { + if (this && isActiveAndEnabled) + OnEnable(); + else + OnDisable(); + } + + void OnEnable() + { + if (!RenderSettings.skybox && !reflectionSkyOverride) + return; + +#if UNITY_EDITOR + if (!Application.isPlaying) + { + UnityEditor.EditorApplication.delayCall -= Apply; + UnityEditor.EditorApplication.delayCall += Apply; + return; + } +#endif + StartCoroutine(ApplyNextFrame()); + } + + IEnumerator ApplyNextFrame() + { + yield return null; + Apply(); + } + + void Apply() + { + if (!this || !isActiveAndEnabled) + return; + + if (!RenderSettings.skybox && !reflectionSkyOverride) + return; + + RenderSettings.ambientIntensity = environmentLightingIntensity; + + var camGo = new GameObject("__SkyboxEnvCam") { hideFlags = HideFlags.HideAndDontSave }; + try + { + var cam = camGo.AddComponent(); + cam.enabled = false; + cam.cullingMask = 0; + cam.clearFlags = CameraClearFlags.Skybox; + cam.cameraType = CameraType.Reflection; + + var camData = camGo.AddComponent(); + camData.renderPostProcessing = false; + camData.requiresDepthTexture = false; + camData.requiresColorTexture = false; + camData.renderShadows = false; + + if (reflectionSkyOverride) + camGo.AddComponent().material = reflectionSkyOverride; + + camGo.transform.position = transform.position; + + var rt = RenderTexture.GetTemporary(64, 64, 16); + cam.targetTexture = rt; + cam.Render(); + cam.targetTexture = null; + RenderTexture.ReleaseTemporary(rt); + + EnsureReflectionCubemap(); + cam.RenderToCubemap(_reflectionCube); + } + finally + { + DestroyImmediate(camGo); + } + + RenderSettings.defaultReflectionMode = DefaultReflectionMode.Custom; + RenderSettings.customReflectionTexture = _reflectionCube; + RenderSettings.reflectionIntensity = reflectionIntensity; + + RenderSettings.ambientMode = AmbientMode.Skybox; + DynamicGI.UpdateEnvironment(); + } + + void EnsureReflectionCubemap() + { + int size = Mathf.Max(16, reflectionResolution); + + if (!_reflectionCube || _reflectionCube.width != size) + { + if (_reflectionCube) + { + DestroyImmediate(_reflectionCube); + } + + _reflectionCube = new Cubemap(size, TextureFormat.RGBA32, true) + { + name = "SkyboxReflection (generated)" + }; + } + } +} diff --git a/Assets/Scripts/Components/SkyboxEnvironmentUpdater.cs.meta b/Assets/Scripts/Components/SkyboxEnvironmentUpdater.cs.meta new file mode 100644 index 0000000..287330d --- /dev/null +++ b/Assets/Scripts/Components/SkyboxEnvironmentUpdater.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 8ad6962cd13b2914a82ab60384f37a3b \ No newline at end of file