using System.IO; using System.Linq; using UnityEditor; using UnityEngine; using UnityEngine.Rendering; using UnityEngine.Rendering.Universal; namespace Luneraria.EditorTools { /// /// Rendering setup of the project (URP asset and renderer, linear colour space, SSAO since /// U5.4, skinning on the GPU since U5.5), idempotent. /// Unity -batchmode -projectPath . -executeMethod Luneraria.EditorTools.LuneProjectSetup.ConfigureRendering /// public static class LuneProjectSetup { public const string SettingsFolder = "Assets/Luneraria/Settings/Rendering"; public const string PipelinePath = SettingsFolder + "/Luneraria_URP.asset"; public const string RendererPath = SettingsFolder + "/Luneraria_URP_Renderer.asset"; public static void ConfigureRendering() { int code = 0; try { Configure(); } catch (System.Exception error) { Debug.LogError("LUNE_SETUP failed: " + error); code = 1; } EditorApplication.Exit(code); } public static UniversalRenderPipelineAsset Configure() { Directory.CreateDirectory(SettingsFolder); AssetDatabase.Refresh(); var asset = AssetDatabase.LoadAssetAtPath(PipelinePath); if (asset == null) { var renderer = ScriptableObject.CreateInstance(); AssetDatabase.CreateAsset(renderer, RendererPath); ResourceReloader.ReloadAllNullIn(renderer, UniversalRenderPipelineAsset.packagePath); asset = UniversalRenderPipelineAsset.Create(renderer); AssetDatabase.CreateAsset(asset, PipelinePath); } // The Godot sun reaches 190 m with four splits (scripts/luneraria/view/lune_world.gd). asset.shadowDistance = 190f; asset.shadowCascadeCount = 4; asset.supportsHDR = true; asset.msaaSampleCount = 4; asset.useSRPBatcher = true; EditorUtility.SetDirty(asset); Ssao(AssetDatabase.LoadAssetAtPath(RendererPath)); GraphicsSettings.defaultRenderPipeline = asset; int current = QualitySettings.GetQualityLevel(); for (int level = 0; level < QualitySettings.names.Length; level++) { QualitySettings.SetQualityLevel(level, false); QualitySettings.renderPipeline = asset; } QualitySettings.SetQualityLevel(current, false); PlayerSettings.colorSpace = ColorSpace.Linear; // U5.5: skinned figures deform on the GPU in batches (compute shaders). With CPU // skinning 35 Meshy Kohlenbeißer (23,000 vertices each) cost about 1 ms of main and // 1.7 ms of render thread per frame. PlayerSettings.meshDeformation = MeshDeformation.GPUBatched; MoveInto("Assets/UniversalRenderPipelineGlobalSettings.asset", SettingsFolder + "/UniversalRenderPipelineGlobalSettings.asset"); MoveInto("Assets/DefaultVolumeProfile.asset", SettingsFolder + "/DefaultVolumeProfile.asset"); AssetDatabase.SaveAssets(); Debug.Log("LUNE_SETUP rendering: " + PipelinePath + ", colour space " + PlayerSettings.colorSpace + ", skinning " + PlayerSettings.meshDeformation); return asset; } /// SSAO intensity, radius (m) and fade distance (m) for the top-down camera (U5.4). public const float SsaoIntensity = 1.2f; public const float SsaoRadius = .35f; public const float SsaoFalloff = 260f; /// /// Screen-space ambient occlusion (U5.4): contact shadows of figures and buildings on the /// ground and occlusion in crevices, the depth cue of current renderers. Radius in metres /// for figures of 1–2 m seen from 20–100 m; it fades only beyond the overview distance. /// The feature is a sub-asset of the renderer and listed in its feature map; updated in place. /// static void Ssao(UniversalRendererData renderer) { if (renderer == null) return; var feature = renderer.rendererFeatures.OfType().FirstOrDefault(); if (feature == null) { feature = ScriptableObject.CreateInstance(); feature.name = "ScreenSpaceAmbientOcclusion"; AssetDatabase.AddObjectToAsset(feature, renderer); AssetDatabase.TryGetGUIDAndLocalFileIdentifier(feature, out string _, out long localId); var data = new SerializedObject(renderer); SerializedProperty list = data.FindProperty("m_RendererFeatures"); list.arraySize++; list.GetArrayElementAtIndex(list.arraySize - 1).objectReferenceValue = feature; SerializedProperty map = data.FindProperty("m_RendererFeatureMap"); map.arraySize++; map.GetArrayElementAtIndex(map.arraySize - 1).longValue = localId; data.ApplyModifiedPropertiesWithoutUndo(); } var settings = new SerializedObject(feature); settings.FindProperty("m_Settings.Intensity").floatValue = SsaoIntensity; settings.FindProperty("m_Settings.Radius").floatValue = SsaoRadius; settings.FindProperty("m_Settings.Falloff").floatValue = SsaoFalloff; settings.FindProperty("m_Settings.DirectLightingStrength").floatValue = .25f; // Depth source after the opaques: URP copies the depth it already has, so SSAO needs no // second geometry pass (the depth-normals prepass cost 19–28 % frame rate on the M5). settings.FindProperty("m_Settings.Source").enumValueIndex = 0; settings.FindProperty("m_Settings.Downsample").boolValue = true; settings.FindProperty("m_Settings.AfterOpaque").boolValue = true; settings.ApplyModifiedPropertiesWithoutUndo(); feature.SetActive(System.Environment.GetEnvironmentVariable("LUNE_SSAO") != "0"); EditorUtility.SetDirty(feature); EditorUtility.SetDirty(renderer); } static void MoveInto(string from, string to) { if (File.Exists(from) && !File.Exists(to)) { string error = AssetDatabase.MoveAsset(from, to); if (!string.IsNullOrEmpty(error)) Debug.LogWarning("LUNE_SETUP move " + from + ": " + error); } } } }