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);
}
}
}
}