363 lines
15 KiB
C#
363 lines
15 KiB
C#
using System;
|
|
using System.IO;
|
|
using System.Text;
|
|
using GameCult.Aetheria.State.Verse;
|
|
using UnityEditor;
|
|
using UnityEditor.SceneManagement;
|
|
using UnityEngine;
|
|
using UnityEngine.SceneManagement;
|
|
|
|
[InitializeOnLoad]
|
|
public static class AetheriaRenderProbe
|
|
{
|
|
private const string RelativeOutputDirectory = "Library/AetheriaRenderProbe";
|
|
private const string ReferenceCaptureSessionKey = "AetheriaRenderProbe.CaptureArpgReference";
|
|
private const string ReferenceCaptureDeadlineSessionKey = "AetheriaRenderProbe.CaptureArpgReference.Deadline";
|
|
|
|
private static string ProjectRoot =>
|
|
Path.GetFullPath(Path.Combine(Application.dataPath, ".."));
|
|
|
|
private static string OutputDirectory =>
|
|
Path.Combine(ProjectRoot, RelativeOutputDirectory);
|
|
|
|
private static string RequestPath =>
|
|
Path.Combine(OutputDirectory, "request.txt");
|
|
|
|
static AetheriaRenderProbe()
|
|
{
|
|
EditorApplication.update += Poll;
|
|
EditorApplication.update += CaptureArpgWhenReady;
|
|
}
|
|
|
|
private static void Poll()
|
|
{
|
|
if (!File.Exists(RequestPath))
|
|
return;
|
|
|
|
if (EditorApplication.isCompiling || EditorApplication.isUpdating)
|
|
return;
|
|
|
|
File.Delete(RequestPath);
|
|
EditorApplication.delayCall += CaptureRequested;
|
|
}
|
|
|
|
private static void CaptureRequested()
|
|
{
|
|
try
|
|
{
|
|
Capture();
|
|
}
|
|
catch (Exception error)
|
|
{
|
|
Directory.CreateDirectory(OutputDirectory);
|
|
File.WriteAllText(
|
|
Path.Combine(OutputDirectory, "error.txt"),
|
|
error.ToString());
|
|
Debug.LogException(error);
|
|
}
|
|
}
|
|
|
|
[MenuItem("Aetheria/Rendering/Capture Render Probe")]
|
|
public static void Capture()
|
|
{
|
|
Directory.CreateDirectory(OutputDirectory);
|
|
|
|
var summary = new StringBuilder();
|
|
summary.AppendLine($"CapturedAtUtc: {DateTime.UtcNow:O}");
|
|
summary.AppendLine($"ActiveScene: {SceneManager.GetActiveScene().path}");
|
|
summary.AppendLine($"Playing: {EditorApplication.isPlaying}");
|
|
summary.AppendLine();
|
|
|
|
var camera = ResolveCamera();
|
|
summary.AppendLine(camera != null
|
|
? $"Camera: {camera.name} orthographic={camera.orthographic} size={camera.orthographicSize} pixel={camera.pixelWidth}x{camera.pixelHeight}"
|
|
: "Camera: <none>");
|
|
|
|
if (camera != null)
|
|
VolumeSampling.RenderForCamera(camera);
|
|
|
|
if (camera != null)
|
|
DumpCamera(camera, summary);
|
|
|
|
DumpVolumeSamplers(summary);
|
|
DumpVolumeCloudRenderers(camera, summary);
|
|
DumpGlobalTexture(summary, "_NebulaSurfaceHeight", "nebula-surface-height.png");
|
|
DumpGlobalTexture(summary, "_NebulaPatchHeight", "nebula-patch-height.png");
|
|
DumpGlobalTexture(summary, "_NebulaPatch", "nebula-patch.png");
|
|
DumpGlobalTexture(summary, "_NebulaTint", "nebula-tint.png");
|
|
DumpGlobalTexture(summary, "_AetheriaGravityHeight", "gravity-height.png");
|
|
|
|
File.WriteAllText(Path.Combine(OutputDirectory, "summary.txt"), summary.ToString());
|
|
AssetDatabase.Refresh();
|
|
Debug.Log($"Aetheria render probe wrote {Path.GetFullPath(OutputDirectory)}");
|
|
}
|
|
|
|
public static void CaptureArpgReference()
|
|
{
|
|
EditorSceneManager.OpenScene("Assets/Scenes/ARPG.unity", OpenSceneMode.Single);
|
|
AetheriaUrpBootstrap.EnsureUrpAssetsAndAssign();
|
|
SessionState.SetBool(ReferenceCaptureSessionKey, true);
|
|
SessionState.SetString(
|
|
ReferenceCaptureDeadlineSessionKey,
|
|
(EditorApplication.timeSinceStartup + 15.0).ToString("R", System.Globalization.CultureInfo.InvariantCulture));
|
|
EditorApplication.EnterPlaymode();
|
|
}
|
|
|
|
private static void CaptureArpgWhenReady()
|
|
{
|
|
if (!SessionState.GetBool(ReferenceCaptureSessionKey, false))
|
|
return;
|
|
if (!EditorApplication.isPlaying || EditorApplication.isCompiling)
|
|
return;
|
|
|
|
var rendered = false;
|
|
foreach (var renderer in Resources.FindObjectsOfTypeAll<VolumeCloudRenderer>())
|
|
{
|
|
if (renderer != null && renderer.gameObject.scene.IsValid() && renderer.DebugRenderCount > 0)
|
|
{
|
|
rendered = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
var deadlineText = SessionState.GetString(ReferenceCaptureDeadlineSessionKey, "0");
|
|
double.TryParse(
|
|
deadlineText,
|
|
System.Globalization.NumberStyles.Float,
|
|
System.Globalization.CultureInfo.InvariantCulture,
|
|
out var deadline);
|
|
if (!rendered && EditorApplication.timeSinceStartup < deadline)
|
|
return;
|
|
|
|
SessionState.SetBool(ReferenceCaptureSessionKey, false);
|
|
try
|
|
{
|
|
Capture();
|
|
EditorApplication.Exit(0);
|
|
}
|
|
catch (Exception error)
|
|
{
|
|
Directory.CreateDirectory(OutputDirectory);
|
|
File.WriteAllText(Path.Combine(OutputDirectory, "error.txt"), error.ToString());
|
|
Debug.LogException(error);
|
|
EditorApplication.Exit(1);
|
|
}
|
|
}
|
|
|
|
private static Camera ResolveCamera()
|
|
{
|
|
if (Camera.main != null)
|
|
return Camera.main;
|
|
|
|
foreach (var camera in Resources.FindObjectsOfTypeAll<Camera>())
|
|
{
|
|
if (camera == null ||
|
|
!camera.gameObject.scene.IsValid() ||
|
|
!camera.gameObject.scene.isLoaded ||
|
|
!camera.enabled)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
return camera;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private static void DumpVolumeCloudRenderers(Camera camera, StringBuilder summary)
|
|
{
|
|
summary.AppendLine();
|
|
summary.AppendLine("VolumeCloudRenderers:");
|
|
|
|
foreach (var renderer in Resources.FindObjectsOfTypeAll<VolumeCloudRenderer>())
|
|
{
|
|
if (renderer == null ||
|
|
!renderer.gameObject.scene.IsValid() ||
|
|
!renderer.gameObject.scene.isLoaded)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
summary.AppendLine($"- {renderer.name} enabled={renderer.enabled} active={renderer.gameObject.activeInHierarchy}");
|
|
summary.AppendLine($" registeredForCamera={VolumeCloudRenderer.TryGetRenderer(camera, out var registered) && registered == renderer}");
|
|
summary.AppendLine($" renderCount={renderer.DebugRenderCount} lastFrame={renderer.DebugLastRenderFrame} firstFrame={renderer.DebugFirstFrame}");
|
|
summary.AppendLine($" Undersample={TextureFacts(renderer.DebugUndersampleBuffer)}");
|
|
summary.AppendLine($" History={TextureFacts(renderer.DebugHistoryBuffer)}");
|
|
summary.AppendLine($" CurrentCloud={TextureFacts(renderer.DebugCurrentCloudBuffer)}");
|
|
|
|
if (renderer.DebugUndersampleBuffer != null)
|
|
SaveTexture(renderer.DebugUndersampleBuffer, Path.Combine(OutputDirectory, "cloud-undersample.png"));
|
|
if (renderer.DebugHistoryBuffer != null)
|
|
SaveTexture(renderer.DebugHistoryBuffer, Path.Combine(OutputDirectory, "cloud-history.png"));
|
|
if (renderer.DebugCurrentCloudBuffer != null)
|
|
SaveTexture(renderer.DebugCurrentCloudBuffer, Path.Combine(OutputDirectory, "cloud-current.png"));
|
|
}
|
|
}
|
|
|
|
private static void DumpVolumeSamplers(StringBuilder summary)
|
|
{
|
|
summary.AppendLine();
|
|
summary.AppendLine("VolumeSamplers:");
|
|
|
|
foreach (var sampler in Resources.FindObjectsOfTypeAll<VolumeSampling>())
|
|
{
|
|
if (sampler == null ||
|
|
!sampler.gameObject.scene.IsValid() ||
|
|
!sampler.gameObject.scene.isLoaded)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
summary.AppendLine($"- {sampler.name} enabled={sampler.enabled} active={sampler.gameObject.activeInHierarchy}");
|
|
summary.AppendLine($" GridCamera={NameOf(sampler.GridCamera)} GridMesh={NameOf(sampler.GridMesh)}");
|
|
summary.AppendLine($" SplatLayerRenderer={NameOf(sampler.SplatLayerRenderer)}");
|
|
summary.AppendLine($" Surface={TextureFacts(sampler.NebulaSurfaceHeight)}");
|
|
summary.AppendLine($" PatchHeight={TextureFacts(sampler.NebulaPatchHeight)}");
|
|
summary.AppendLine($" Patch={TextureFacts(sampler.NebulaPatch)}");
|
|
summary.AppendLine($" Tint={TextureFacts(sampler.NebulaTint)}");
|
|
DumpSamplerEnvironment(sampler, summary);
|
|
SaveTextureExr(sampler.NebulaSurfaceHeight, Path.Combine(OutputDirectory, "fossil-nebula-surface-height.exr"));
|
|
SaveTextureExr(sampler.NebulaPatchHeight, Path.Combine(OutputDirectory, "fossil-nebula-patch-height.exr"));
|
|
SaveTextureExr(sampler.NebulaPatch, Path.Combine(OutputDirectory, "fossil-nebula-patch.exr"));
|
|
SaveTextureExr(sampler.NebulaTint, Path.Combine(OutputDirectory, "fossil-nebula-tint.exr"));
|
|
}
|
|
}
|
|
|
|
private static void DumpSamplerEnvironment(VolumeSampling sampler, StringBuilder summary)
|
|
{
|
|
var settings = sampler.Settings;
|
|
var environment = settings != null ? settings.DefaultEnvironment : null;
|
|
summary.AppendLine($" Settings={NameOf(settings)}");
|
|
if (environment == null)
|
|
{
|
|
summary.AppendLine(" Environment=<none>");
|
|
return;
|
|
}
|
|
|
|
summary.AppendLine(
|
|
$" Nebula FillDensity={environment.Nebula.FillDensity} FillDistance={environment.Nebula.FillDistance} FillExponent={environment.Nebula.FillExponent} FillOffset={environment.Nebula.FillOffset}");
|
|
summary.AppendLine(
|
|
$" Nebula FloorOffset={environment.Nebula.FloorOffset} FloorBlend={environment.Nebula.FloorBlend} PatchDensity={environment.Nebula.PatchDensity} PatchBlend={environment.Nebula.PatchBlend}");
|
|
summary.AppendLine(
|
|
$" Nebula Luminance={environment.Nebula.Luminance} Extinction={environment.Nebula.Extinction} TintLodExponent={environment.Nebula.TintLodExponent} SafetyDistance={environment.Nebula.SafetyDistance}");
|
|
summary.AppendLine(
|
|
$" Lighting DynamicSkyBoost={environment.Lighting.DynamicSkyBoost} DynamicLodHigh={environment.Lighting.DynamicLodHigh} DynamicLodLow={environment.Lighting.DynamicLodLow} DynamicIntensity={environment.Lighting.DynamicIntensity}");
|
|
summary.AppendLine(
|
|
$" Noise Scale={environment.Noise.Scale} Amplitude={environment.Noise.Amplitude} Exponent={environment.Noise.Exponent} Speed={environment.Noise.Speed} SlopeExponent={environment.Noise.SlopeExponent}");
|
|
summary.AppendLine(
|
|
$" Flow GlobalScale={environment.Flow.GlobalScale} GlobalAmplitude={environment.Flow.GlobalAmplitude} GlobalScrollSpeed={environment.Flow.GlobalScrollSpeed} Period={environment.Flow.Period} SlopeAmplitude={environment.Flow.SlopeAmplitude} SwirlAmplitude={environment.Flow.SwirlAmplitude}");
|
|
summary.AppendLine(
|
|
$" Grid Enabled={environment.Grid.Enabled} Offset={environment.Grid.Offset}");
|
|
}
|
|
|
|
private static void DumpGlobalTexture(StringBuilder summary, string globalName, string fileName)
|
|
{
|
|
var texture = Shader.GetGlobalTexture(globalName);
|
|
summary.AppendLine($"{globalName}: {TextureFacts(texture)}");
|
|
if (texture != null)
|
|
SaveTexture(texture, Path.Combine(OutputDirectory, fileName));
|
|
}
|
|
|
|
private static void DumpCamera(Camera camera, StringBuilder summary)
|
|
{
|
|
var width = Mathf.Max(1, camera.pixelWidth > 0 ? camera.pixelWidth : 1920);
|
|
var height = Mathf.Max(1, camera.pixelHeight > 0 ? camera.pixelHeight : 1080);
|
|
var target = RenderTexture.GetTemporary(width, height, 24, RenderTextureFormat.ARGB32);
|
|
var previousTarget = camera.targetTexture;
|
|
var previousActive = RenderTexture.active;
|
|
try
|
|
{
|
|
camera.targetTexture = target;
|
|
camera.Render();
|
|
SaveRenderTexture(target, Path.Combine(OutputDirectory, "camera-render.png"));
|
|
summary.AppendLine($"CameraRender: {width}x{height} -> camera-render.png");
|
|
}
|
|
finally
|
|
{
|
|
camera.targetTexture = previousTarget;
|
|
RenderTexture.active = previousActive;
|
|
RenderTexture.ReleaseTemporary(target);
|
|
}
|
|
}
|
|
|
|
private static void SaveTexture(Texture texture, string path)
|
|
{
|
|
var width = Mathf.Max(1, texture.width);
|
|
var height = Mathf.Max(1, texture.height);
|
|
var target = RenderTexture.GetTemporary(width, height, 0, RenderTextureFormat.ARGB32);
|
|
var previous = RenderTexture.active;
|
|
try
|
|
{
|
|
Graphics.Blit(texture, target);
|
|
SaveRenderTexture(target, path);
|
|
}
|
|
finally
|
|
{
|
|
RenderTexture.active = previous;
|
|
RenderTexture.ReleaseTemporary(target);
|
|
}
|
|
}
|
|
|
|
private static void SaveTextureExr(Texture texture, string path)
|
|
{
|
|
if (texture == null)
|
|
return;
|
|
var width = Mathf.Max(1, texture.width);
|
|
var height = Mathf.Max(1, texture.height);
|
|
var target = RenderTexture.GetTemporary(width, height, 0, RenderTextureFormat.ARGBFloat);
|
|
var previous = RenderTexture.active;
|
|
try
|
|
{
|
|
Graphics.Blit(texture, target);
|
|
RenderTexture.active = target;
|
|
var image = new Texture2D(width, height, TextureFormat.RGBAFloat, false, true);
|
|
try
|
|
{
|
|
image.ReadPixels(new Rect(0, 0, width, height), 0, 0);
|
|
image.Apply();
|
|
File.WriteAllBytes(path, image.EncodeToEXR(Texture2D.EXRFlags.OutputAsFloat));
|
|
}
|
|
finally
|
|
{
|
|
UnityEngine.Object.DestroyImmediate(image);
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
RenderTexture.active = previous;
|
|
RenderTexture.ReleaseTemporary(target);
|
|
}
|
|
}
|
|
|
|
private static void SaveRenderTexture(RenderTexture target, string path)
|
|
{
|
|
var previous = RenderTexture.active;
|
|
RenderTexture.active = target;
|
|
var image = new Texture2D(target.width, target.height, TextureFormat.RGBA32, false);
|
|
try
|
|
{
|
|
image.ReadPixels(new Rect(0, 0, target.width, target.height), 0, 0);
|
|
image.Apply();
|
|
File.WriteAllBytes(path, image.EncodeToPNG());
|
|
}
|
|
finally
|
|
{
|
|
UnityEngine.Object.DestroyImmediate(image);
|
|
RenderTexture.active = previous;
|
|
}
|
|
}
|
|
|
|
private static string NameOf(UnityEngine.Object value)
|
|
{
|
|
return value != null ? value.name : "<none>";
|
|
}
|
|
|
|
private static string TextureFacts(Texture texture)
|
|
{
|
|
if (texture == null)
|
|
return "<none>";
|
|
|
|
return $"{texture.name} {texture.width}x{texture.height} {texture.GetType().Name}";
|
|
}
|
|
}
|