유니티에서 런타임 도중 원할 때 스카이박스와 환경 설정(안개, 디렉셔널라이트 등)을 편리하게 바꾸는 매니저를 구현하여 정리하였습니다.
💬 서론
- 스카이박스를 블렌드 할 쉐이더는 [여기]에 있는 파일을 사용하였습니다.
- 모든 기능이 포함된 프로젝트를 [깃허브]에 업로드하였습니다. 다운로드받아 사용하실 수 있습니다.
- URP에서 테스트 하였으며, 다른 파이프라인에서는 테스트하지 않았습니다.
✅ 구현
· 스크립트
더보기
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Enviroment;
public class EnviromentManager : MonoBehaviour
{
#region Singleton
private static EnviromentManager _instance = null;
public static EnviromentManager Instance
{
get
{
if (_instance == null)
_instance = FindObjectOfType<EnviromentManager>();
return _instance;
}
}
#endregion
[Header("Directional Light(Sun) From Scene")]
[SerializeField] private Light mDirectionalLight;
[Header("Skybox Rotate Speed")]
[SerializeField] private float mSkyboxRotSpeed;
[Space(30)]
[Header("Preload Enviroment Preset")]
[SerializeField] private EnviromentPreset[] mEnviromentPresets;
private Dictionary<string, EnviromentPreset> mPreloadEnviromentPresets = new Dictionary<string, EnviromentPreset>();
private EnviromentPreset? mCurrentPreset = null, mPrevPreset = null; // Current & Prev preset
private Material mSkyboxMat; // Scene's skybox material
private float mCurrentSkyboxRot; // Store the current rotation angle
private Coroutine mCoBlendEnviroment; // Control blend coroutine
private void Awake()
{
// Instance skybox material
mSkyboxMat = new Material(RenderSettings.skybox);
RenderSettings.skybox = mSkyboxMat;
// Get skybox rotation
mCurrentSkyboxRot = mSkyboxMat.GetFloat("_Rotation");
// Load preload presets
foreach (EnviromentPreset preset in mEnviromentPresets)
mPreloadEnviromentPresets.Add(preset.name, preset);
// Set "mCurrentPreset" from initial scene options
EnviromentPreset currentPreset = ScriptableObject.CreateInstance<EnviromentPreset>();
currentPreset.LoadCurrentSettings();
mCurrentPreset = currentPreset;
}
private void Update()
{
mCurrentSkyboxRot += Time.deltaTime * mSkyboxRotSpeed;
if (mCurrentSkyboxRot > 360f)
mCurrentSkyboxRot -= 360f;
mSkyboxMat.SetFloat("_Rotation", mCurrentSkyboxRot);
}
public bool TryInvertEnviromentPreset(float duration)
{
if (mPrevPreset == null || mCurrentPreset == null)
{
Debug.LogWarning("Not Preset Loaded!");
return false;
}
BlendEnviroment(mPrevPreset, duration);
return true;
}
public void BlendEnviroment(string key, float duration)
{
this.BlendEnviroment(mPreloadEnviromentPresets[key], duration);
}
public void BlendEnviroment(EnviromentPreset preset, float duration)
{
if (mCoBlendEnviroment is not null)
StopCoroutine(mCoBlendEnviroment);
mCoBlendEnviroment = StartCoroutine(CoBlendEnviroment(preset, duration));
}
private IEnumerator CoBlendEnviroment(EnviromentPreset preset, float duration)
{
// Store Current & Prev preset
mPrevPreset = mCurrentPreset;
mCurrentPreset = preset;
// Get current option state
EnviromentPreset curState = ScriptableObject.CreateInstance<EnviromentPreset>();
curState.LightningIntensityMultiplier = RenderSettings.ambientIntensity;
curState.ReflectionsIntensityMultiplier = RenderSettings.reflectionIntensity;
float currentBlendValue = mSkyboxMat.GetFloat("_Blend");
Color currentSunColor = mDirectionalLight.color;
float currentSunIntensity = mDirectionalLight.intensity;
Color currentFogColor = RenderSettings.fogColor;
float currentFogStart = RenderSettings.fogStartDistance;
float currentFogEnd = RenderSettings.fogEndDistance;
// Load blend target textures to skybox mat
mSkyboxMat.SetTexture("_FrontTex2", preset.SidedSkyboxPreset.FrontTex);
mSkyboxMat.SetTexture("_BackTex2", preset.SidedSkyboxPreset.BackTex);
mSkyboxMat.SetTexture("_LeftTex2", preset.SidedSkyboxPreset.LeftTex);
mSkyboxMat.SetTexture("_RightTex2", preset.SidedSkyboxPreset.RightTex);
mSkyboxMat.SetTexture("_UpTex2", preset.SidedSkyboxPreset.UpTex);
mSkyboxMat.SetTexture("_DownTex2", preset.SidedSkyboxPreset.DownTex);
// Blend processes
float process = 0f;
while (process < 1f)
{
process += Time.deltaTime / duration;
// Enviroment Intensity Blend
RenderSettings.ambientIntensity = Mathf.Lerp(curState.LightningIntensityMultiplier, preset.LightningIntensityMultiplier, process);
RenderSettings.reflectionIntensity = Mathf.Lerp(curState.ReflectionsIntensityMultiplier, preset.ReflectionsIntensityMultiplier, process);
// Fog Blend
RenderSettings.fogColor = Color.Lerp(currentFogColor, preset.FogPreset.FogColor, process);
RenderSettings.fogStartDistance = Mathf.Lerp(currentFogStart, preset.FogPreset.FogStart, process);
RenderSettings.fogEndDistance = Mathf.Lerp(currentFogEnd, preset.FogPreset.FogEnd, process);
// Directional Light(Sun) Blend
mDirectionalLight.color = Color.Lerp(currentSunColor, preset.SunPreset.SunColor, process);
mDirectionalLight.intensity = Mathf.Lerp(currentSunIntensity, preset.SunPreset.SunIntensity, process);
// Skybox Blend
mSkyboxMat.SetFloat("_Blend", Mathf.Lerp(currentBlendValue, 1.0f, process));
yield return null;
}
// Load blended preset textures to base texture
mSkyboxMat.SetTexture("_FrontTex", preset.SidedSkyboxPreset.FrontTex);
mSkyboxMat.SetTexture("_BackTex", preset.SidedSkyboxPreset.BackTex);
mSkyboxMat.SetTexture("_LeftTex", preset.SidedSkyboxPreset.LeftTex);
mSkyboxMat.SetTexture("_RightTex", preset.SidedSkyboxPreset.RightTex);
mSkyboxMat.SetTexture("_UpTex", preset.SidedSkyboxPreset.UpTex);
mSkyboxMat.SetTexture("_DownTex", preset.SidedSkyboxPreset.DownTex);
mSkyboxMat.SetFloat("_Blend", 0f);
}
}
[Header("Directional Light(Sun) From Scene")]
[SerializeField] private Light mDirectionalLight;
[Header("Skybox Rotate Speed")]
[SerializeField] private float mSkyboxRotSpeed;
[Space(30)]
[Header("Preload Enviroment Preset")]
[SerializeField] private EnviromentPreset[] mEnviromentPresets;
- Scene에서 사용할 디렉셔널 라이트와 SkyBox 머티리얼의 회전속도를 지정합니다.
- mEnviromentPresets는 런타임도중 변경하고싶은 환경설정 프리셋을 사용할 수 있도록 합니다.
private Dictionary<string, EnviromentPreset> mPreloadEnviromentPresets = new Dictionary<string, EnviromentPreset>();
private EnviromentPreset? mCurrentPreset = null, mPrevPreset = null; // Current & Prev preset
- 딕셔너리는 mEnviromentPresets를 로드하여 키로 바로 찾을 수 있도록 하기위해 사용합니다.
- mCurrentPreset, mPrevPreset은 현재 프리셋, 변경 직전의 프리셋을 임시로 저장합니다.
private void Awake()
- 기본적인 초기화를 진행합니다.
- Scene의 SkyBox 머티리얼을 가져오고 현재 회전값을 가져옵니다.
- 현재 Scene의 설정값을 임시 Preset으로 가져오고 이를 mCurrentPreset으로 저장합니다.
private void Update()
- 매 프레임마다 Skybox 머티리얼이 회전하도록 합니다.
public bool TryInvertEnviromentPreset(float duration)
- mPrevPreset을 mCurrentPreset으로 설정하도록 합니다.
public void BlendEnviroment(string key, float duration)
- mEnviromentPresets에 넣은 프리셋들을 key를 이용하여 찾은 후 이를 현재 Scene에 적용하도록 합니다.
private IEnumerator CoBlendEnviroment(EnviromentPreset preset, float duration)
- 코루틴을 사용하여 블렌드를 합니다.
더보기
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace Enviroment
{
[System.Serializable]
public class SunPreset
{
[Header("Controls brightness of directional light")]
[SerializeField] public float SunIntensity;
[Header("Controls directional light color")]
[SerializeField] public Color SunColor;
}
[System.Serializable]
public class FogPreset
{
[Header("Controls distance of fog")]
[SerializeField] public float FogStart, FogEnd;
[Header("Controls fog color")]
[SerializeField] public Color FogColor;
}
[System.Serializable]
public class SidedSkyboxPreset
{
[Header("Front [+Z]")]
[SerializeField] public Texture FrontTex;
[Header("Front [-Z]")]
[SerializeField] public Texture BackTex;
[Header("Left [+X]")]
[SerializeField] public Texture LeftTex;
[Header("Right [-Z]")]
[SerializeField] public Texture RightTex;
[Header("Up [+Y]")]
[SerializeField] public Texture UpTex;
[Header("Down [-Y]")]
[SerializeField] public Texture DownTex;
}
[System.Serializable]
[CreateAssetMenu(fileName = "EnviromentPreset", menuName = "Preset/EnviromentPreset")]
public class EnviromentPreset : ScriptableObject
{
[Header("Contains the 6 side texture of the Skybox to be replaced")]
[SerializeField] public SidedSkyboxPreset SidedSkyboxPreset = null;
[Header("Controls fog option")]
[SerializeField] public FogPreset FogPreset = null;
[Header("Controls directional light(sun) option")]
[SerializeField] public SunPreset SunPreset = null;
[Header("Controls the brightness of the skybox lighting in the Scene")]
[Range(0f, 8f)][SerializeField] public float LightningIntensityMultiplier = 1.0f;
[Header("Controls how much the skybox affects reflections in the Scene.")]
[Range(0f, 1f)][SerializeField] public float ReflectionsIntensityMultiplier = 1.0f;
public void LoadCurrentSettings()
{
try
{
// get render setting values
this.LightningIntensityMultiplier = RenderSettings.ambientIntensity;
this.ReflectionsIntensityMultiplier = RenderSettings.reflectionIntensity;
// try get fog options
if (RenderSettings.fogMode != FogMode.Linear)
Debug.LogWarning("FogPreset was skipped. Fog mode is not Linear");
else
{
FogPreset fogPreset = new FogPreset();
fogPreset.FogStart = RenderSettings.fogStartDistance;
fogPreset.FogEnd = RenderSettings.fogEndDistance;
fogPreset.FogColor = RenderSettings.fogColor;
this.FogPreset = fogPreset;
}
// try find directional light in scene via named "Directional Light"
GameObject? directionalLightGo = GameObject.Find("Directional Light");
if (directionalLightGo is null)
Debug.LogWarning("SunPreset was skipped. Cannot find \"Directional Light\" in scene");
else
{
Light directionalLight = directionalLightGo.GetComponent<Light>();
if (directionalLight is null)
Debug.LogWarning("SunPreset was skipped. Cannot find light component in \"Directional Light\" object");
else
{
SunPreset sunPreset = new SunPreset();
sunPreset.SunIntensity = directionalLight.intensity;
if (directionalLight.useColorTemperature)
Debug.LogWarning("SunPreset.SunColor was skipped. Light.useColorTemperature is true");
else
sunPreset.SunColor = directionalLight.color;
this.SunPreset = sunPreset;
}
}
// try get textures from skybox's textures from scene
Material mat = RenderSettings.skybox;
SidedSkyboxPreset sidedSkyboxPreset = new SidedSkyboxPreset();
try
{
sidedSkyboxPreset.FrontTex = mat.GetTexture("_FrontTex");
sidedSkyboxPreset.BackTex = mat.GetTexture("_BackTex");
sidedSkyboxPreset.LeftTex = mat.GetTexture("_LeftTex");
sidedSkyboxPreset.RightTex = mat.GetTexture("_RightTex");
sidedSkyboxPreset.UpTex = mat.GetTexture("_UpTex");
sidedSkyboxPreset.DownTex = mat.GetTexture("_DownTex");
}
catch (System.Exception e)
{
Debug.LogError($"<color=orange>Preset Cannot Loaded! Did you load skybox material completely on RenderSettings?</color>");
throw (e);
}
this.SidedSkyboxPreset = sidedSkyboxPreset;
#if UNITY_EDITOR
Debug.Log($"<color=cyan>Preset \"{this.name}\" completely loaded.</color>");
#endif
}
catch (System.Exception e)
{
Debug.LogError(e);
}
}
}
#region Unity Editor
#if UNITY_EDITOR
[CustomEditor(typeof(EnviromentPreset))]
public class QuestManager_EditorFunctions : Editor
{
private EnviromentPreset baseTarget; // EnviromentPreset
void OnEnable() { baseTarget = (EnviromentPreset)target; }
public override void OnInspectorGUI()
{
base.OnInspectorGUI();
GUIStyle style = new GUIStyle();
style.richText = true;
GUILayout.Label("\n\n<b><size=16><color=cyan>Load the settings of the current scene into this preset</color></size></b>", style);
if (GUILayout.Button("Load to preset!"))
{
baseTarget.LoadCurrentSettings();
}
GUILayout.Label("\n\n<b><size=16><color=cyan>Set this preset to scene</color></size></b>", style);
if (GUILayout.Button("Preset to scene!"))
{
this.PresetToScene();
}
}
private void PresetToScene()
{
// Enviroment Intensity
RenderSettings.ambientIntensity = baseTarget.LightningIntensityMultiplier;
RenderSettings.reflectionIntensity = baseTarget.ReflectionsIntensityMultiplier;
// Fog
RenderSettings.fogColor = baseTarget.FogPreset.FogColor;
RenderSettings.fogStartDistance = baseTarget.FogPreset.FogStart;
RenderSettings.fogEndDistance = baseTarget.FogPreset.FogEnd;
try // Directional Light(Sun) Blend via find object name "Directional Light"
{
Light directionalLight = GameObject.Find("Directional Light").GetComponent<Light>();
directionalLight.color = baseTarget.SunPreset.SunColor;
directionalLight.intensity = baseTarget.SunPreset.SunIntensity;
}
catch(System.Exception e)
{
Debug.LogError(e);
}
// Skybox
Material skyboxMat = RenderSettings.skybox;
skyboxMat.SetTexture("_FrontTex", baseTarget.SidedSkyboxPreset.FrontTex);
skyboxMat.SetTexture("_BackTex", baseTarget.SidedSkyboxPreset.BackTex);
skyboxMat.SetTexture("_LeftTex", baseTarget.SidedSkyboxPreset.LeftTex);
skyboxMat.SetTexture("_RightTex", baseTarget.SidedSkyboxPreset.RightTex);
skyboxMat.SetTexture("_UpTex", baseTarget.SidedSkyboxPreset.UpTex);
skyboxMat.SetTexture("_DownTex", baseTarget.SidedSkyboxPreset.DownTex);
skyboxMat.SetFloat("_Blend", 0f);
}
}
#endif
#endregion
}
public class SunPreset{...}
- Scene에서 사용되는 Directional Light(Sun)의 설정을 바꿀 프리셋입니다.
- Light Appearance가 Color이어야합니다.
public class FogPreset{...}
- Scene에서 사용되는 Fog의 설정을 바꿀 프리셋입니다.
- Fog의 Mode가 Linear이어야합니다.
public class SidedSkyboxPreset{...}
- Scene에서 사용되는 SkyBox Material의 설정을 바꿀 프리셋입니다.
- 프로젝트에서 사용하는 "VertBlendedSkybox" 쉐이더의 머티리얼을 사용해야합니다.
public class EnviromentPreset : ScriptableObject
{
[Header("Contains the 6 side texture of the Skybox to be replaced")]
[SerializeField] public SidedSkyboxPreset SidedSkyboxPreset = null;
[Header("Controls fog option")]
[SerializeField] public FogPreset FogPreset = null;
[Header("Controls directional light(sun) option")]
[SerializeField] public SunPreset SunPreset = null;
[Header("Controls the brightness of the skybox lighting in the Scene")]
[Range(0f, 8f)][SerializeField] public float LightningIntensityMultiplier = 1.0f;
[Header("Controls how much the skybox affects reflections in the Scene.")]
[Range(0f, 1f)][SerializeField] public float ReflectionsIntensityMultiplier = 1.0f;
...
}
- 위에서 설명한 세개의 클래스를 제외하고 RenderSettings의 설정을 바꾸는 float 변수가 두개 있습니다.
✅ 사용
1. VertBlendedSkybox 쉐이더를 사용하는 머티리얼을 SkyBox 머티리얼로 설정합니다.
- 이 쉐이더 및 머티리얼 파일은 깃허브 프로젝트에 업로드 되어있습니다.
- 또는 [원작자의 페이지]에서 받을 수 있습니다.
2. 프리셋을 저장할 스크립터블 오브젝트를 생성합니다.
- 프로젝트에서 Create > Preset < EnviromentPreset을 눌러 새 스크립터블 오브젝트를 생성합니다.
3. 스크립터블 오브젝트 파일의 Skybox Preset을 알맞게 삽입합니다.
- 6side에 맞게 이미지파일 6장을 잘 넣어줍니다.
4. 씬에 EnviromentManager.cs를 사용하는 오브젝트를 생성합니다.
- 생성한 프리셋들을 Presets에 삽입합니다.
- Directional Light와 RotSpeed 또한 설정합니다.
5. 스크립트를 호출하여 설정한 프리셋을 이용하여 Sky Blend를 완료합니다.
public class SampleScript : MonoBehaviour
{
private void Start()
{
StartCoroutine(CoBlendSkies());
}
private IEnumerator CoBlendSkies()
{
while (true)
{
EnviromentManager.Instance.BlendEnviroment("Mid", 5.0f);
yield return new WaitForSeconds(10.0f);
EnviromentManager.Instance.BlendEnviroment("Night", 5.0f);
yield return new WaitForSeconds(10.0f);
EnviromentManager.Instance.BlendEnviroment("Day", 5.0f);
yield return new WaitForSeconds(10.0f);
}
}
}
- EnviromentManager.Instance.BlendEnviroment("Mid", 5.0f); 와 같이 싱글톤으로 에셋파일의 이름을 키로하여 호출할 수 있습니다.
✅ 결과
💬 참조
- 깃허브에 올린 프로젝트는 무료 에셋을 이용하여 업로드하였습니다.
'unity game modules' 카테고리의 다른 글
[유니티] 자동 스크롤 (0) | 2023.03.22 |
---|---|
[유니티] 외곽선 쉐이더 구현 및 관리 (0) | 2023.03.21 |
[유니티] 검기 이펙트, VFX 관리 (0) | 2023.03.07 |
[유니티] 발소리 구현 (2) | 2023.03.04 |
[유니티] 구글 스프레드 시트(엑셀) 연동 3 - 데이터 가져오기 (0) | 2023.03.02 |