需求:光照二十四小时实时变化,昼夜系统。
思路:程序化天空盒,绑定一系列参数到配置文件。
实现:
一,程序化天空盒
1,太阳(月亮)随时间旋转:采样贴图贴在天空盒上,直接用天空盒UV会有拉伸问题,采样的UV需要C#传矩阵到Shader。
创建太阳,月亮,灯光的引用
[SerializeField] private Transform m_sunTransform = null;
[SerializeField] private Transform m_moonTransform = null;
[SerializeField] private Transform m_directionLight = null;
传到Shader里采样
private Material skyMat = null;
......
internal static readonly int SunMatrix = Shader.PropertyToID("_SunMatrix");
internal static readonly int MoonMatrix = Shader.PropertyToID("_MoonMatrix");
......
skyMat.SetMatrix(SunMatrix, m_sunTransform.worldToLocalMatrix);
skyMat.SetMatrix(MoonMatrix, m_moonTransform.worldToLocalMatrix);
天空球Shader
uniform float4x4 _SunMatrix;
uniform float4x4 _MoonMatrix;
......
o.vertex = UnityObjectToClipPos(v.vertex);
o.SunPos = mul((float3x3)_SunMatrix, v.vertex.xyz);
o.MoonPos = mul((float3x3)_MoonMatrix, v.vertex.xyz);
......
half4 sunColor = tex2D(_SunTex, i.SunPos + 0.5);
half4 moonColor = tex2D( _MoonTex, i.MoonPos + 0.5);
......
采样出来图像有些许拉伸,UV加0.5。在朝向对面也会生成了一个图像,需要做个mask。
C#部分
internal static readonly int UpDirectionMatrix = Shader.PropertyToID("_UpDirectionMatrix");
internal static readonly int SunDirection = Shader.PropertyToID("_SunDirection");
internal static readonly int MoonDirection = Shader.PropertyToID("_MoonDirection");
......
skyMat.SetMatrix(UpDirectionMatrix, transform.worldToLocalMatrix);
skyMat.SetVector(SunDirection,transform.InverseTransformDirection(-m_sunTransform.forward));
skyMat.SetVector(MoonDirection,transform.InverseTransformDirection(-m_moonTransform.forward));
Shader部分
uniform float4x4 _UpDirectionMatrix;
......
o.WorldPos = normalize(mul((float3x3)unity_WorldToObject, v.vertex.xyz));
o.WorldPos = normalize(mul((float3x3)_UpDirectionMatrix, o.WorldPos));
......
float3 viewDir = normalize(i.WorldPos);
float sunCosTheta = dot(viewDir, _SunDirection);
float moonCosTheta = dot(viewDir, _MoonDirection);
half4 sunColor = tex2D(_SunTex, i.SunPos + 0.5) * saturate(sunCosTheta);
half4 moonColor = tex2D( _MoonTex, i.MoonPos + 0.5) * saturate(moonCosTheta);
白天时,灯光朝向与太阳一致,晚上时,灯光朝向与月亮一致,太阳朝向与Y轴的夹角大于0为白天,小于0为夜晚。太阳和月亮朝向相反。
m_moonTransform.forward = -m_sunTransform.forward;
m_sunElevation = Vector3.Dot(-m_sunTransform.forward, Vector3.up);
m_directionLight.localRotation=Quaternion.LookRotation(m_sunElevation >= 0.0f ? m_sunTransform.forward : m_moonTransform.forward);
现在旋转m_sunTransform已经能够看到灯光随太阳(月亮)正常变化了,然后加入时间。
[SerializeField,Range(0,24)] private float dayTime = 8;
[SerializeField,Range(0,3600)] private float timeScale = 1;
......
private void Update()
{
dayTime += Time.deltaTime/3600 * timeScale;
if (dayTime > 24)
{
dayTime = 0;
}
m_sunTransform.localRotation=Quaternion.Euler(dayTime * 15,300,240);
}
2,大气散射:理论网上有很多了,这里直接写实现。
C#部分
public float wavelengthR = 680;
public float wavelengthG = 550;
public float wavelengthB = 450;
public float molecularDensity = 2.545f;
public float rayleigh = 1.5f;
public float kr = 8.4f;
public float km = 1.2f;
public float mie = 1.0f;
public float mieDistance = 1.0f;
public float scattering = 0.25f;
public float luminance = 1.5f;
public float exposure = 2.0f;
public Color rayleighColor ;
public Color mieColor ;
public Color scatteringColor;
public float fogScatteringScale = 1.0f;
......
internal static readonly int ScatteringModeID = Shader.PropertyToID("_ScatteringMode");
internal static readonly int Kr = Shader.PropertyToID("_Kr");
internal static readonly int Km = Shader.PropertyToID("_Km");
internal static readonly int Rayleigh = Shader.PropertyToID("_Rayleigh");
internal static readonly int Mie = Shader.PropertyToID("_Mie");
internal static readonly int MieDistance = Shader.PropertyToID("_MieDepth");
internal static readonly int Scattering = Shader.PropertyToID("_Scattering");
internal static readonly int Luminance = Shader.PropertyToID("_Luminance");
internal static readonly int Exposure = Shader.PropertyToID("_Exposure");
internal static readonly int RayleighColor = Shader.PropertyToID("_RayleighColor");
internal static readonly int MieColor = Shader.PropertyToID("_MieColor");
internal static readonly int ScatteringColor = Shader.PropertyToID("_ScatteringColor");
internal static readonly int FogScatteringScale = Shader.PropertyToID("_FogScatteringScale");
......
skyMat.SetInt(ScatteringModeID,(int)m_scatteringMode);
skyMat.SetFloat(Kr, kr * 1000f);
skyMat.SetFloat(Km, km * 1000f);
skyMat.SetVector(Rayleigh, ComputeRayleigh() * rayleigh);
skyMat.SetVector(Mie, ComputeMie() * mie);
skyMat.SetFloat(MieDistance, mieDistance);
skyMat.SetFloat(Scattering, scattering * 60f);
skyMat.SetFloat(Luminance, luminance);
skyMat.SetFloat(Exposure, exposure);
skyMat.SetColor(RayleighColor, rayleighColor);
skyMat.SetColor(MieColor, mieColor);
skyMat.SetColor(ScatteringColor, scatteringColor);
skyMat.SetFloat(FogScatteringScale,fogScatteringScale);
......
private Vector3 ComputeRayleigh()
{
Vector3 rayleigh = Vector3.one;
Vector3 lambda = new Vector3(wavelengthR, wavelengthG, wavelengthB) * 1e-9f;
float n = 1.0003f;
float pn = 0.035f;
float n2 = n * n;
float N = molecularDensity * 1E25f;
float temp = (8.0f * Mathf.PI * Mathf.PI * Mathf.PI * ((n2 - 1.0f) * (n2 - 1.0f))) / (3.0f * N) * ((6.0f + 3.0f * pn) / (6.0f - 7.0f * pn));
rayleigh.x = temp / Mathf.Pow(lambda.x, 4.0f);
rayleigh.y = temp / Mathf.Pow(lambda.y, 4.0f);
rayleigh.z = temp / Mathf.Pow(lambda.z, 4.0f);
return rayleigh;
}
private Vector3 ComputeMie()
{
Vector3 mie;
float c = (0.6544f * 5.0f - 0.6510f) * 10f * 1e-9f;
Vector3 k = new Vector3(686.0f, 678.0f, 682.0f);
mie.x = (434.0f * c * Mathf.PI * Mathf.Pow((4.0f * Mathf.PI) / wavelengthR, 2.0f) * k.x);
mie.y = (434.0f * c * Mathf.PI * Mathf.Pow((4.0f * Mathf.PI) / wavelengthG, 2.0f) * k.y);
mie.z = (434.0f * c * Mathf.PI * Mathf.Pow((4.0f * Mathf.PI) / wavelengthB, 2.0f) * k.z);
return mie;
}
......
public enum ScatteringMode
{
Automatic,
Custom
}
Shader部分
#define PI 3.1415926535
#define Pi316 0.0596831
#define Pi14 0.07957747
#define MieG float3(0.4375f, 1.5625f, 1.5f)
......
uniform float _FogScatteringScale;
uniform int _ScatteringMode;
uniform float _Kr;
uniform float _Km;
uniform float3 _Rayleigh;
uniform float3 _Mie;
uniform float _MieDistance;
uniform float _Scattering;
uniform float _Luminance;
uniform float _Exposure;
uniform float4 _RayleighColor;
uniform float4 _MieColor;
uniform float4 _ScatteringColor;
......
float zenith = acos(saturate(dot(float3(0.0, 1.0, 0.0), viewDir))) * _FogScatteringScale;
float z = (cos(zenith) + 0.15 * pow(93.885 - ((zenith * 180.0f) / PI), -1.253));
float SR = _Kr / z;
float SM = _Km / z;
float3 fex = exp(-(_Rayleigh * SR + _Mie * SM));
float sunset = clamp(dot(float3(0.0, 1.0, 0.0), _SunDirection), 0.0, 0.5);
float3 Esun = _ScatteringMode == 0 ? lerp(fex, (1.0 - fex), sunset) : _ScatteringColor;
//Sun Inscatter
float rayPhase = 2.0 + 0.5 * pow(sunCosTheta, 2.0);
float miePhase = MieG.x / pow(MieG.y - MieG.z * sunCosTheta, 1.5);
float3 BrTheta = Pi316 * _Rayleigh * rayPhase * _RayleighColor;
float3 BmTheta = Pi14 * _Mie * miePhase * _MieColor * sunRise;
float3 BrmTheta = (BrTheta + BmTheta) / (_Rayleigh + _Mie);
float3 sunInScatter = BrmTheta * Esun * _Scattering * (1.0 - fex);
sunInScatter *= sunRise;
//Moon Inscatter
rayPhase = 2.0 + 0.5 * pow(moonCosTheta, 2.0);
miePhase = MieG.x / pow(MieG.y - MieG.z * moonCosTheta, 1.5);
BrTheta = Pi316 * _Rayleigh * rayPhase * _RayleighColor;
BmTheta = Pi14 * _Mie * miePhase * _MieColor * moonRise;
BrmTheta = (BrTheta + BmTheta) / (_Rayleigh + _Mie);
Esun = _ScatteringMode == 0 ? (1.0 - fex) : _ScatteringColor;
float3 moonInScatter = BrmTheta * Esun * _Scattering * 0.1 * (1.0 - fex);
moonInScatter *= 1.0 - sunRise;
BrmTheta = BrTheta / (_Rayleigh + _Mie);
float3 skyLuminance = BrmTheta * _ScatteringColor * _Luminance * (1.0 - fex);
值用float,颜色用Color,是一个定制,无法满足随时间变化而变化,用AnimationCurve和Gradient替换,AnimationCurve.Evaluate(Time time),Gradient.Evaluate(Time time)。然后参数可以存到ScriptableObject里,创建多个ScriptableObject存不同的天气参数,用来实现不同天气的切换,这里我就不再写了。