Files
unity/Assets/Game/Scripts/Rendering/WorldLighting.cs
T
RCiBot eb819c6688 feat: improve map lighting and expand the playable training range
Add real-time lighting and shadows, training courses and targets, and animated salvage crates. Include bilingual assets and curated verification results.
2026-11-12 18:03:04 +08:00

179 lines
8.3 KiB
C#

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;
namespace Ironspore
{
public sealed class WorldLighting : MonoBehaviour
{
readonly List<Light> fixtures = new List<Light>(2);
WorldLightingProfile profile;
Material sky;
ReflectionProbe environment;
Camera observer;
bool ownsProfile;
int shadowBudget;
bool captureQueued, capturing;
float captureAfter;
public bool ReflectionReady { get; private set; }
public void Initialize(WorldLightingProfile settings, bool hangar)
{
profile = settings;
if (!profile)
{
profile = ScriptableObject.CreateInstance<WorldLightingProfile>();
ownsProfile = true;
}
int level = QualitySettings.GetQualityLevel();
shadowBudget = level <= 1 ? 0 : level >= 4 ? 2 : 1;
RenderSettings.ambientMode = AmbientMode.Trilight;
RenderSettings.ambientSkyColor = profile.ambientSky;
RenderSettings.ambientEquatorColor = profile.ambientEquator;
RenderSettings.ambientGroundColor = profile.ambientGround;
RenderSettings.fog = !hangar;
RenderSettings.fogMode = FogMode.Linear;
RenderSettings.fogColor = profile.fogColor;
RenderSettings.fogStartDistance = profile.fogStart;
RenderSettings.fogEndDistance = profile.fogEnd;
RenderSettings.reflectionIntensity = profile.reflectionIntensity;
var sourceSky = RenderSettings.skybox;
sky = sourceSky ? new Material(sourceSky) : new Material(Shader.Find("Skybox/Procedural"));
sky.name = "RuntimeDaySky";
if (sky.HasProperty("_Exposure")) sky.SetFloat("_Exposure", profile.skyExposure);
RenderSettings.skybox = sky;
var sunObject = new GameObject("Sun");
sunObject.transform.SetParent(transform, false);
sunObject.transform.rotation = Quaternion.Euler(profile.sunEuler);
var sun = sunObject.AddComponent<Light>();
sun.type = LightType.Directional;
sun.color = profile.sunColor;
sun.intensity = hangar ? profile.hangarSunIntensity : profile.sunIntensity;
sun.renderMode = LightRenderMode.ForcePixel;
sun.shadows = level <= 1 ? LightShadows.Hard : LightShadows.Soft;
sun.shadowStrength = 1;
sun.shadowBias = profile.bias;
sun.shadowNormalBias = profile.normalBias;
sun.shadowNearPlane = profile.shadowNearPlane;
sun.shadowCustomResolution = level <= 1 ? 1024 : level >= 4 ? 4096 : 2048;
RenderSettings.sun = sun;
}
public void CompleteEnvironment(Camera camera, bool hangar)
{
observer = camera;
if (hangar)
{
WorldFactory.Box("HangarWorkFixture", new Vector3(2, 8.2f, 1), new Vector3(2, .18f, .65f), new Color(.8f, .87f, 1), transform, false, true);
AddSpot("HangarWorkLight", new Vector3(2, 8, 1), new Vector3(2, 1, 2), new Color(.82f, .9f, 1), profile.hangarIntensity, 17, 84);
}
else
{
WorldFactory.Box("NavigationWorkFixture", new Vector3(0, 3.4f, 22.7f), new Vector3(1.2f, .16f, .4f), new Color(.55f, 1, .85f), transform, false, true);
AddSpot("NavigationWorkLight", new Vector3(0, 3.25f, 22.3f), new Vector3(0, 0, 20.6f), new Color(.65f, 1, .85f), profile.navigationIntensity, 11, 78);
WorldFactory.Box("ExtractionLampPost", new Vector3(-22.4f, 3.1f, -8), new Vector3(.16f, 6.2f, .16f), new Color(.2f, .25f, .28f), transform, false);
WorldFactory.Box("ExtractionLampArm", new Vector3(-20.7f, 6.1f, -8), new Vector3(3.5f, .15f, .18f), new Color(.2f, .25f, .28f), transform, false);
WorldFactory.Box("ExtractionWorkFixture", new Vector3(-19, 6, -8), new Vector3(1, .18f, .6f), new Color(1, .77f, .45f), transform, false, true);
AddSpot("ExtractionWorkLight", new Vector3(-19, 5.8f, -8), new Vector3(-19, 0, -8), new Color(1, .8f, .55f), profile.extractionIntensity, 13, 76);
}
var probeObject = new GameObject("EnvironmentReflection");
probeObject.transform.SetParent(transform, false);
probeObject.transform.position = hangar ? new Vector3(2, 3, 2) : new Vector3(0, 3, 8);
environment = probeObject.AddComponent<ReflectionProbe>();
environment.mode = ReflectionProbeMode.Realtime;
environment.refreshMode = ReflectionProbeRefreshMode.ViaScripting;
environment.timeSlicingMode = ReflectionProbeTimeSlicingMode.IndividualFaces;
environment.resolution = QualitySettings.GetQualityLevel() >= 4 ? 256 : 128;
environment.hdr = true;
environment.boxProjection = true;
environment.blendDistance = 5;
environment.size = hangar ? new Vector3(66, 18, 45) : new Vector3(74, 20, 104);
// The captured environment must not freeze actors, transient props or world labels.
environment.cullingMask = ~((1 << 8) | (1 << 9) | (1 << 10) | (1 << 11));
environment.nearClipPlane = .2f;
environment.farClipPlane = 150;
environment.intensity = profile.reflectionIntensity;
SelectLights();
StartCoroutine(CaptureEnvironment());
}
void AddSpot(string name, Vector3 position, Vector3 target, Color color, float intensity, float range, float angle)
{
var obj = new GameObject(name);
obj.transform.SetParent(transform, false);
obj.transform.position = position;
obj.transform.LookAt(target);
var light = obj.AddComponent<Light>();
light.type = LightType.Spot;
light.renderMode = LightRenderMode.ForcePixel;
light.color = color;
light.intensity = intensity;
light.range = range;
light.spotAngle = angle;
light.innerSpotAngle = angle * .65f;
light.shadows = LightShadows.Soft;
light.shadowCustomResolution = 1024;
light.shadowBias = profile.bias;
light.shadowNormalBias = profile.normalBias;
light.shadowNearPlane = profile.shadowNearPlane;
light.enabled = false;
fixtures.Add(light);
}
void LateUpdate()
{
SelectLights();
if (environment && captureQueued && !capturing && Time.unscaledTime >= captureAfter)
StartCoroutine(CaptureEnvironment());
}
void SelectLights()
{
if (!observer)
return;
Vector3 position = observer.transform.position;
float limit = profile.localLightCullDistance * profile.localLightCullDistance;
Light nearest = null;
float distance = limit;
foreach (var light in fixtures)
{
float candidate = (light.transform.position - position).sqrMagnitude;
if (candidate < distance) { distance = candidate; nearest = light; }
}
foreach (var light in fixtures)
{
bool active = shadowBudget > 0 && (light == nearest || shadowBudget > 1 && (light.transform.position - position).sqrMagnitude < limit);
if (light.enabled == active)
continue;
light.enabled = active;
captureQueued = true;
}
}
IEnumerator CaptureEnvironment()
{
capturing = true;
captureQueued = false;
yield return null;
DynamicGI.UpdateEnvironment();
int render = environment.RenderProbe();
while (environment && !environment.IsFinishedRendering(render))
yield return null;
ReflectionReady = environment != null;
capturing = false;
captureAfter = Time.unscaledTime + 8;
}
void OnDestroy()
{
if (sky)
{
if (RenderSettings.skybox == sky) RenderSettings.skybox = null;
Destroy(sky);
}
if (ownsProfile && profile) Destroy(profile);
}
}
}