Integrate Aetheria reactive state worktree
This commit is contained in:
parent
426cd8f656
commit
65133ac8a0
61 changed files with 3427 additions and 402 deletions
|
|
@ -67,8 +67,8 @@ public sealed class AetheriaDaemonIndirectRenderer : MonoBehaviour
|
|||
foreach (var renderGroup in renderGroups)
|
||||
{
|
||||
_seenGroupIds.Add(renderGroup.GroupId);
|
||||
if (!assetCatalog.TryResolveMesh(renderGroup.MeshKey, out var mesh) ||
|
||||
!assetCatalog.TryResolveMaterial(renderGroup.MaterialKey, out var material))
|
||||
if (!TryResolveMesh(renderGroup, out var mesh) ||
|
||||
!TryResolveMaterial(renderGroup, out var material))
|
||||
{
|
||||
if (logMissingAssets)
|
||||
{
|
||||
|
|
@ -237,4 +237,28 @@ public sealed class AetheriaDaemonIndirectRenderer : MonoBehaviour
|
|||
|
||||
_staleGroupIds.Clear();
|
||||
}
|
||||
|
||||
private bool TryResolveMesh(AetheriaRuntimeDaemonRenderGroupDocument renderGroup, out Mesh mesh)
|
||||
{
|
||||
if (renderGroup?.MeshAsset != null &&
|
||||
!string.IsNullOrWhiteSpace(renderGroup.MeshAsset.AssetKey) &&
|
||||
assetCatalog.TryResolveMesh(renderGroup.MeshAsset, out mesh))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return assetCatalog.TryResolveMesh(renderGroup?.MeshKey, out mesh);
|
||||
}
|
||||
|
||||
private bool TryResolveMaterial(AetheriaRuntimeDaemonRenderGroupDocument renderGroup, out Material material)
|
||||
{
|
||||
if (renderGroup?.MaterialAsset != null &&
|
||||
!string.IsNullOrWhiteSpace(renderGroup.MaterialAsset.AssetKey) &&
|
||||
assetCatalog.TryResolveMaterial(renderGroup.MaterialAsset, out material))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return assetCatalog.TryResolveMaterial(renderGroup?.MaterialKey, out material);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using GameCult.Aetheria.State.Verse;
|
||||
using UnityEngine;
|
||||
|
||||
[CreateAssetMenu(
|
||||
|
|
@ -33,6 +34,11 @@ public sealed class AetheriaDaemonRenderAssetCatalog : ScriptableObject
|
|||
return mesh != null;
|
||||
}
|
||||
|
||||
public bool TryResolveMesh(AetheriaRuntimeAssetRef asset, out Mesh mesh)
|
||||
{
|
||||
return TryResolveMesh(ResolveLocalKey(asset), out mesh);
|
||||
}
|
||||
|
||||
public bool TryResolveMaterial(string key, out Material material)
|
||||
{
|
||||
material = null;
|
||||
|
|
@ -54,6 +60,22 @@ public sealed class AetheriaDaemonRenderAssetCatalog : ScriptableObject
|
|||
return material != null;
|
||||
}
|
||||
|
||||
public bool TryResolveMaterial(AetheriaRuntimeAssetRef asset, out Material material)
|
||||
{
|
||||
return TryResolveMaterial(ResolveLocalKey(asset), out material);
|
||||
}
|
||||
|
||||
public bool TryResolveTexture(AetheriaRuntimeAssetRef asset, out Texture2D texture)
|
||||
{
|
||||
texture = null;
|
||||
var key = ResolveLocalKey(asset);
|
||||
if (string.IsNullOrWhiteSpace(key))
|
||||
return false;
|
||||
|
||||
texture = Resources.Load<Texture2D>(NormalizeResourceKey(key));
|
||||
return texture != null;
|
||||
}
|
||||
|
||||
private static string NormalizeResourceKey(string key)
|
||||
{
|
||||
const string resourcesPrefix = "resources://";
|
||||
|
|
@ -65,6 +87,17 @@ public sealed class AetheriaDaemonRenderAssetCatalog : ScriptableObject
|
|||
return key;
|
||||
}
|
||||
|
||||
private static string ResolveLocalKey(AetheriaRuntimeAssetRef asset)
|
||||
{
|
||||
if (asset == null)
|
||||
return "";
|
||||
|
||||
if (string.Equals(asset.Transport, AetheriaRuntimeAssetTransports.Resources, StringComparison.OrdinalIgnoreCase))
|
||||
return asset.Uri;
|
||||
|
||||
return string.IsNullOrWhiteSpace(asset.Uri) ? asset.AssetKey : asset.Uri;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
private struct MeshEntry
|
||||
{
|
||||
|
|
|
|||
137
Assets/Scripts/Gameplay/AetheriaRenderSplatBuffer.cs
Normal file
137
Assets/Scripts/Gameplay/AetheriaRenderSplatBuffer.cs
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
using GameCult.Aetheria.State.Verse;
|
||||
using Unity.Mathematics;
|
||||
using UnityEngine;
|
||||
|
||||
public sealed class AetheriaRenderSplatBuffer : System.IDisposable
|
||||
{
|
||||
public const string SplatBufferPropertyName = "_AetheriaSplats";
|
||||
public const string SplatCountPropertyName = "_AetheriaSplatCount";
|
||||
public const string ViewportToClipPropertyName = "_AetheriaViewportToClip";
|
||||
public const string ChannelFilterPropertyName = "_AetheriaChannelFilter";
|
||||
|
||||
private const int SplatStrideBytes = 80;
|
||||
|
||||
private AetheriaRenderSplat[] _splats = System.Array.Empty<AetheriaRenderSplat>();
|
||||
private GraphicsBuffer _buffer;
|
||||
private int _capacity;
|
||||
|
||||
public int Count { get; private set; }
|
||||
public GraphicsBuffer Buffer => _buffer;
|
||||
public bool HasGpuBuffer => _buffer != null && _buffer.IsValid() && Count > 0;
|
||||
|
||||
public bool Upload(AetheriaRuntimeRenderSplatSoa splats, int channelFilter = -1, int layerFilter = -1)
|
||||
{
|
||||
Count = Pack(splats, channelFilter, layerFilter);
|
||||
EnsureCapacity(Count);
|
||||
if (Count <= 0)
|
||||
return false;
|
||||
|
||||
_buffer.SetData(_splats, 0, 0, Count);
|
||||
return true;
|
||||
}
|
||||
|
||||
private int Pack(AetheriaRuntimeRenderSplatSoa splats, int channelFilter, int layerFilter)
|
||||
{
|
||||
if (splats == null || splats.Count <= 0)
|
||||
return 0;
|
||||
|
||||
EnsureCpuCapacity(splats.Count);
|
||||
var count = 0;
|
||||
for (var i = 0; i < splats.Count; i++)
|
||||
{
|
||||
var channel = Read(splats.Channel, i, 0);
|
||||
if (channelFilter >= 0 && channel != channelFilter)
|
||||
continue;
|
||||
|
||||
var layerIndex = Read(splats.LayerIndex, i, -1);
|
||||
if (layerFilter >= 0 && layerIndex != layerFilter)
|
||||
continue;
|
||||
|
||||
var halfExtentX = (float)Read(splats.HalfExtentX, i, 0);
|
||||
var halfExtentY = (float)Read(splats.HalfExtentY, i, 0);
|
||||
if (halfExtentX <= 0 || halfExtentY <= 0)
|
||||
continue;
|
||||
|
||||
_splats[count++] = new AetheriaRenderSplat
|
||||
{
|
||||
CenterHalfExtent = new float4(
|
||||
(float)Read(splats.CenterX, i, 0),
|
||||
(float)Read(splats.CenterY, i, 0),
|
||||
halfExtentX,
|
||||
halfExtentY),
|
||||
RotationChannelFalloff = new float4(
|
||||
(float)Read(splats.RotationCos, i, 1),
|
||||
(float)Read(splats.RotationSin, i, 0),
|
||||
channel,
|
||||
Read(splats.Falloff, i, AetheriaRuntimeRenderSplatFalloffs.Smooth)),
|
||||
LayerSource = new float4(
|
||||
layerIndex,
|
||||
Read(splats.SourceKind, i, AetheriaRuntimeRenderSplatSourceKinds.Constant),
|
||||
(float)Read(splats.AnimationSpeed, i, 0),
|
||||
(float)Read(splats.SourceFlags, i, 0)),
|
||||
SourceFrequencyPhase = new float4(
|
||||
(float)Read(splats.FrequencyX, i, 1),
|
||||
(float)Read(splats.FrequencyY, i, 1),
|
||||
(float)Read(splats.PhaseX, i, 0),
|
||||
(float)Read(splats.PhaseY, i, 0)),
|
||||
Value = new float4(
|
||||
(float)Read(splats.ValueR, i, 0),
|
||||
(float)Read(splats.ValueG, i, 0),
|
||||
(float)Read(splats.ValueB, i, 0),
|
||||
(float)Read(splats.ValueA, i, 1))
|
||||
};
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
private void EnsureCpuCapacity(int count)
|
||||
{
|
||||
if (_splats.Length >= count)
|
||||
return;
|
||||
|
||||
_splats = new AetheriaRenderSplat[math.ceilpow2(math.max(1, count))];
|
||||
}
|
||||
|
||||
private void EnsureCapacity(int count)
|
||||
{
|
||||
if (_buffer != null && _buffer.IsValid() && _capacity >= count)
|
||||
return;
|
||||
|
||||
_buffer?.Release();
|
||||
_buffer = null;
|
||||
_capacity = 0;
|
||||
if (count <= 0)
|
||||
return;
|
||||
|
||||
_capacity = math.ceilpow2(math.max(1, count));
|
||||
_buffer = new GraphicsBuffer(GraphicsBuffer.Target.Structured, _capacity, SplatStrideBytes);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_buffer?.Release();
|
||||
_buffer = null;
|
||||
_capacity = 0;
|
||||
Count = 0;
|
||||
}
|
||||
|
||||
private static double Read(System.Collections.Generic.IReadOnlyList<double> values, int index, double fallback)
|
||||
{
|
||||
return values != null && index >= 0 && index < values.Count ? values[index] : fallback;
|
||||
}
|
||||
|
||||
private static int Read(System.Collections.Generic.IReadOnlyList<int> values, int index, int fallback)
|
||||
{
|
||||
return values != null && index >= 0 && index < values.Count ? values[index] : fallback;
|
||||
}
|
||||
|
||||
private struct AetheriaRenderSplat
|
||||
{
|
||||
public float4 CenterHalfExtent;
|
||||
public float4 RotationChannelFalloff;
|
||||
public float4 LayerSource;
|
||||
public float4 SourceFrequencyPhase;
|
||||
public float4 Value;
|
||||
}
|
||||
}
|
||||
11
Assets/Scripts/Gameplay/AetheriaRenderSplatBuffer.cs.meta
Normal file
11
Assets/Scripts/Gameplay/AetheriaRenderSplatBuffer.cs.meta
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 32e6f17486f34e8c8c5f7640bff319af
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
241
Assets/Scripts/Gameplay/AetheriaRenderSplatLayerRenderer.cs
Normal file
241
Assets/Scripts/Gameplay/AetheriaRenderSplatLayerRenderer.cs
Normal file
|
|
@ -0,0 +1,241 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using GameCult.Aetheria.State.Verse;
|
||||
using Unity.Mathematics;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Experimental.Rendering;
|
||||
using UnityEngine.UI;
|
||||
|
||||
public sealed class AetheriaRenderSplatLayerRenderer : MonoBehaviour
|
||||
{
|
||||
[SerializeField]
|
||||
private AetheriaRenderSplatRasterizer rasterizer;
|
||||
|
||||
[SerializeField]
|
||||
private AetheriaRenderSplatLayerBinding[] bindings =
|
||||
{
|
||||
new AetheriaRenderSplatLayerBinding
|
||||
{
|
||||
LayerKey = AetheriaRuntimeRenderSplatLayerKeys.GravityHeight,
|
||||
MaterialTextureProperty = "_DetailTex",
|
||||
GlobalTextureName = "_AetheriaGravityHeight"
|
||||
},
|
||||
new AetheriaRenderSplatLayerBinding
|
||||
{
|
||||
LayerKey = AetheriaRuntimeRenderSplatLayerKeys.FogTint,
|
||||
MaterialTextureProperty = "_DetailTex",
|
||||
GlobalTextureName = "_NebulaTint"
|
||||
},
|
||||
new AetheriaRenderSplatLayerBinding
|
||||
{
|
||||
LayerKey = AetheriaRuntimeRenderSplatLayerKeys.FogSurfaceHeight,
|
||||
GlobalTextureName = "_NebulaSurfaceHeight",
|
||||
WidthScale = 0.5f,
|
||||
HeightScale = 0.5f
|
||||
},
|
||||
new AetheriaRenderSplatLayerBinding
|
||||
{
|
||||
LayerKey = AetheriaRuntimeRenderSplatLayerKeys.FogPatchHeight,
|
||||
GlobalTextureName = "_NebulaPatchHeight",
|
||||
WidthScale = 0.5f,
|
||||
HeightScale = 0.5f
|
||||
},
|
||||
new AetheriaRenderSplatLayerBinding
|
||||
{
|
||||
LayerKey = AetheriaRuntimeRenderSplatLayerKeys.FogPatch,
|
||||
GlobalTextureName = "_NebulaPatch",
|
||||
WidthScale = 0.5f,
|
||||
HeightScale = 0.5f
|
||||
},
|
||||
new AetheriaRenderSplatLayerBinding
|
||||
{
|
||||
LayerKey = AetheriaRuntimeRenderSplatLayerKeys.Influence,
|
||||
MaterialTextureProperty = "_DetailTex",
|
||||
GlobalTextureName = "_AetheriaInfluence"
|
||||
}
|
||||
};
|
||||
|
||||
private readonly Dictionary<string, RenderTexture> _texturesByLayerKey =
|
||||
new Dictionary<string, RenderTexture>(StringComparer.Ordinal);
|
||||
|
||||
private void Reset()
|
||||
{
|
||||
rasterizer = GetComponent<AetheriaRenderSplatRasterizer>();
|
||||
}
|
||||
|
||||
public bool TryGetTexture(string layerKey, out RenderTexture texture)
|
||||
{
|
||||
return _texturesByLayerKey.TryGetValue(layerKey ?? "", out texture);
|
||||
}
|
||||
|
||||
public void Render(
|
||||
AetheriaRuntimeRenderSplatsViewportDocument document,
|
||||
int width,
|
||||
int height)
|
||||
{
|
||||
if (document == null)
|
||||
return;
|
||||
|
||||
ResolveRasterizer();
|
||||
if (rasterizer == null)
|
||||
return;
|
||||
|
||||
var layers = document.Layers ?? Array.Empty<AetheriaRuntimeRenderSplatLayerDefinition>();
|
||||
for (var layerIndex = 0; layerIndex < layers.Count; layerIndex++)
|
||||
{
|
||||
var layer = layers[layerIndex];
|
||||
if (layer == null || string.IsNullOrWhiteSpace(layer.LayerKey))
|
||||
continue;
|
||||
|
||||
foreach (var binding in bindings ?? Array.Empty<AetheriaRenderSplatLayerBinding>())
|
||||
{
|
||||
if (binding == null ||
|
||||
!binding.Enabled ||
|
||||
!string.Equals(binding.LayerKey, layer.LayerKey, StringComparison.Ordinal))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var texture = EnsureTexture(binding, layer, width, height);
|
||||
rasterizer.RenderLayerToTarget(
|
||||
document,
|
||||
texture,
|
||||
layerIndex,
|
||||
ResolveMaterialPass(layer.BlendMode),
|
||||
ResolveClearColor(layer));
|
||||
ApplyBinding(binding, texture);
|
||||
_texturesByLayerKey[layer.LayerKey] = texture;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ResolveRasterizer()
|
||||
{
|
||||
if (rasterizer != null)
|
||||
return;
|
||||
|
||||
rasterizer = GetComponent<AetheriaRenderSplatRasterizer>();
|
||||
if (rasterizer == null)
|
||||
rasterizer = gameObject.AddComponent<AetheriaRenderSplatRasterizer>();
|
||||
}
|
||||
|
||||
private RenderTexture EnsureTexture(
|
||||
AetheriaRenderSplatLayerBinding binding,
|
||||
AetheriaRuntimeRenderSplatLayerDefinition layer,
|
||||
int width,
|
||||
int height)
|
||||
{
|
||||
var targetWidth = math.max(1, Mathf.RoundToInt(width * Mathf.Max(0.01f, binding.WidthScale)));
|
||||
var targetHeight = math.max(1, Mathf.RoundToInt(height * Mathf.Max(0.01f, binding.HeightScale)));
|
||||
var format = ResolveGraphicsFormat(binding.GraphicsFormatOverride, layer.GraphicsFormat);
|
||||
var existing = binding.TargetTexture;
|
||||
if (existing != null &&
|
||||
existing.width == targetWidth &&
|
||||
existing.height == targetHeight &&
|
||||
existing.graphicsFormat == format)
|
||||
{
|
||||
return existing;
|
||||
}
|
||||
|
||||
if (existing != null)
|
||||
{
|
||||
existing.Release();
|
||||
Destroy(existing);
|
||||
}
|
||||
|
||||
var descriptor = new RenderTextureDescriptor(targetWidth, targetHeight)
|
||||
{
|
||||
depthBufferBits = 0,
|
||||
graphicsFormat = format,
|
||||
msaaSamples = 1,
|
||||
sRGB = false,
|
||||
useMipMap = binding.UseMipMaps,
|
||||
autoGenerateMips = binding.UseMipMaps
|
||||
};
|
||||
binding.TargetTexture = new RenderTexture(descriptor)
|
||||
{
|
||||
name = $"Aetheria {layer.LayerKey}",
|
||||
filterMode = binding.FilterMode,
|
||||
wrapMode = TextureWrapMode.Clamp
|
||||
};
|
||||
binding.TargetTexture.Create();
|
||||
return binding.TargetTexture;
|
||||
}
|
||||
|
||||
private static GraphicsFormat ResolveGraphicsFormat(string bindingOverride, string layerFormat)
|
||||
{
|
||||
var value = string.IsNullOrWhiteSpace(bindingOverride) ? layerFormat : bindingOverride;
|
||||
return Enum.TryParse(value, out GraphicsFormat format)
|
||||
? format
|
||||
: GraphicsFormat.R16_SFloat;
|
||||
}
|
||||
|
||||
private static int ResolveMaterialPass(string blendMode)
|
||||
{
|
||||
switch (blendMode)
|
||||
{
|
||||
case AetheriaRuntimeRenderSplatBlendModes.Max:
|
||||
return 1;
|
||||
case AetheriaRuntimeRenderSplatBlendModes.Alpha:
|
||||
return 2;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
private static Color ResolveClearColor(AetheriaRuntimeRenderSplatLayerDefinition layer)
|
||||
{
|
||||
return new Color(
|
||||
(float)layer.ClearR,
|
||||
(float)layer.ClearG,
|
||||
(float)layer.ClearB,
|
||||
(float)layer.ClearA);
|
||||
}
|
||||
|
||||
private static void ApplyBinding(AetheriaRenderSplatLayerBinding binding, RenderTexture texture)
|
||||
{
|
||||
if (binding.Display != null)
|
||||
{
|
||||
var material = binding.Display.material;
|
||||
if (material != null && !string.IsNullOrWhiteSpace(binding.MaterialTextureProperty))
|
||||
material.SetTexture(binding.MaterialTextureProperty, texture);
|
||||
else
|
||||
binding.Display.material.mainTexture = texture;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(binding.GlobalTextureName))
|
||||
Shader.SetGlobalTexture(binding.GlobalTextureName, texture);
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
foreach (var binding in bindings ?? Array.Empty<AetheriaRenderSplatLayerBinding>())
|
||||
{
|
||||
if (binding?.TargetTexture == null)
|
||||
continue;
|
||||
|
||||
binding.TargetTexture.Release();
|
||||
Destroy(binding.TargetTexture);
|
||||
binding.TargetTexture = null;
|
||||
}
|
||||
|
||||
_texturesByLayerKey.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public sealed class AetheriaRenderSplatLayerBinding
|
||||
{
|
||||
public bool Enabled = true;
|
||||
public string LayerKey = "";
|
||||
public Image Display;
|
||||
public string MaterialTextureProperty = "_DetailTex";
|
||||
public string GlobalTextureName = "";
|
||||
public string GraphicsFormatOverride = "";
|
||||
public float WidthScale = 1.0f;
|
||||
public float HeightScale = 1.0f;
|
||||
public bool UseMipMaps;
|
||||
public FilterMode FilterMode = FilterMode.Bilinear;
|
||||
[NonSerialized]
|
||||
public RenderTexture TargetTexture;
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 0f3c143f0a844f38a109f2de02ce4423
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
300
Assets/Scripts/Gameplay/AetheriaRenderSplatRasterizer.cs
Normal file
300
Assets/Scripts/Gameplay/AetheriaRenderSplatRasterizer.cs
Normal file
|
|
@ -0,0 +1,300 @@
|
|||
using GameCult.Aetheria.State.Verse;
|
||||
using Unity.Mathematics;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Experimental.Rendering;
|
||||
using UnityEngine.Rendering;
|
||||
|
||||
public sealed class AetheriaRenderSplatRasterizer : MonoBehaviour
|
||||
{
|
||||
[SerializeField]
|
||||
private Material splatMaterial;
|
||||
|
||||
[SerializeField]
|
||||
private RenderTexture targetTexture;
|
||||
|
||||
[SerializeField]
|
||||
private GraphicsFormat targetFormat = GraphicsFormat.R16_SFloat;
|
||||
|
||||
[SerializeField]
|
||||
private int width = 512;
|
||||
|
||||
[SerializeField]
|
||||
private int height = 512;
|
||||
|
||||
[SerializeField]
|
||||
private int channelFilter = -1;
|
||||
|
||||
[SerializeField]
|
||||
private int layerFilter = -1;
|
||||
|
||||
[SerializeField]
|
||||
private int materialPass;
|
||||
|
||||
[SerializeField]
|
||||
private Color clearColor = Color.clear;
|
||||
|
||||
[SerializeField]
|
||||
private bool clearBeforeDraw = true;
|
||||
|
||||
private readonly AetheriaRenderSplatBuffer _buffer = new AetheriaRenderSplatBuffer();
|
||||
private CommandBuffer _commandBuffer;
|
||||
private RenderTexture _ownedTargetTexture;
|
||||
private Material _ownedSplatMaterial;
|
||||
private static readonly int SplatBufferPropertyId =
|
||||
Shader.PropertyToID(AetheriaRenderSplatBuffer.SplatBufferPropertyName);
|
||||
private static readonly int SplatCountPropertyId =
|
||||
Shader.PropertyToID(AetheriaRenderSplatBuffer.SplatCountPropertyName);
|
||||
private static readonly int ViewportToClipPropertyId =
|
||||
Shader.PropertyToID(AetheriaRenderSplatBuffer.ViewportToClipPropertyName);
|
||||
private static readonly int ChannelFilterPropertyId =
|
||||
Shader.PropertyToID(AetheriaRenderSplatBuffer.ChannelFilterPropertyName);
|
||||
|
||||
public RenderTexture TargetTexture => targetTexture != null ? targetTexture : _ownedTargetTexture;
|
||||
public int ChannelFilter
|
||||
{
|
||||
get => channelFilter;
|
||||
set => channelFilter = value;
|
||||
}
|
||||
|
||||
public int LayerFilter
|
||||
{
|
||||
get => layerFilter;
|
||||
set => layerFilter = value;
|
||||
}
|
||||
|
||||
public int LastDrawnCount { get; private set; }
|
||||
|
||||
public RenderTexture Render(
|
||||
AetheriaRuntimeRenderSplatsViewportDocument document,
|
||||
int overrideWidth = 0,
|
||||
int overrideHeight = 0)
|
||||
{
|
||||
if (document == null)
|
||||
{
|
||||
LastDrawnCount = 0;
|
||||
return TargetTexture;
|
||||
}
|
||||
|
||||
var output = EnsureTarget(overrideWidth, overrideHeight);
|
||||
var material = ResolveMaterial();
|
||||
if (output == null || material == null)
|
||||
{
|
||||
LastDrawnCount = 0;
|
||||
return output;
|
||||
}
|
||||
|
||||
_buffer.Upload(document.Splats, channelFilter, layerFilter);
|
||||
LastDrawnCount = _buffer.Count;
|
||||
if (!_buffer.HasGpuBuffer)
|
||||
{
|
||||
if (clearBeforeDraw)
|
||||
Clear(output);
|
||||
return output;
|
||||
}
|
||||
|
||||
_commandBuffer ??= new CommandBuffer { name = "Aetheria Render Splats" };
|
||||
_commandBuffer.Clear();
|
||||
_commandBuffer.SetRenderTarget(output);
|
||||
if (clearBeforeDraw)
|
||||
_commandBuffer.ClearRenderTarget(false, true, clearColor);
|
||||
|
||||
_commandBuffer.SetGlobalBuffer(SplatBufferPropertyId, _buffer.Buffer);
|
||||
_commandBuffer.SetGlobalInt(SplatCountPropertyId, _buffer.Count);
|
||||
_commandBuffer.SetGlobalInt(ChannelFilterPropertyId, channelFilter);
|
||||
_commandBuffer.SetGlobalMatrix(ViewportToClipPropertyId, BuildViewportToClip(document.Viewport));
|
||||
_commandBuffer.DrawProcedural(
|
||||
Matrix4x4.identity,
|
||||
material,
|
||||
math.max(0, materialPass),
|
||||
MeshTopology.Triangles,
|
||||
6,
|
||||
_buffer.Count);
|
||||
Graphics.ExecuteCommandBuffer(_commandBuffer);
|
||||
return output;
|
||||
}
|
||||
|
||||
public RenderTexture Render(
|
||||
AetheriaRuntimeRenderSplatsViewportDocument document,
|
||||
int overrideWidth,
|
||||
int overrideHeight,
|
||||
int overrideChannelFilter)
|
||||
{
|
||||
var previousChannelFilter = channelFilter;
|
||||
channelFilter = overrideChannelFilter;
|
||||
try
|
||||
{
|
||||
return Render(document, overrideWidth, overrideHeight);
|
||||
}
|
||||
finally
|
||||
{
|
||||
channelFilter = previousChannelFilter;
|
||||
}
|
||||
}
|
||||
|
||||
public RenderTexture RenderLayer(
|
||||
AetheriaRuntimeRenderSplatsViewportDocument document,
|
||||
int overrideWidth,
|
||||
int overrideHeight,
|
||||
int overrideLayerFilter,
|
||||
int overrideMaterialPass,
|
||||
GraphicsFormat overrideTargetFormat,
|
||||
Color overrideClearColor)
|
||||
{
|
||||
var previousLayerFilter = layerFilter;
|
||||
var previousMaterialPass = materialPass;
|
||||
var previousTargetFormat = targetFormat;
|
||||
var previousClearColor = clearColor;
|
||||
layerFilter = overrideLayerFilter;
|
||||
materialPass = overrideMaterialPass;
|
||||
targetFormat = overrideTargetFormat;
|
||||
clearColor = overrideClearColor;
|
||||
try
|
||||
{
|
||||
return Render(document, overrideWidth, overrideHeight);
|
||||
}
|
||||
finally
|
||||
{
|
||||
layerFilter = previousLayerFilter;
|
||||
materialPass = previousMaterialPass;
|
||||
targetFormat = previousTargetFormat;
|
||||
clearColor = previousClearColor;
|
||||
}
|
||||
}
|
||||
|
||||
public RenderTexture RenderLayerToTarget(
|
||||
AetheriaRuntimeRenderSplatsViewportDocument document,
|
||||
RenderTexture output,
|
||||
int overrideLayerFilter,
|
||||
int overrideMaterialPass,
|
||||
Color overrideClearColor)
|
||||
{
|
||||
var previousTargetTexture = targetTexture;
|
||||
var previousLayerFilter = layerFilter;
|
||||
var previousMaterialPass = materialPass;
|
||||
var previousClearColor = clearColor;
|
||||
targetTexture = output;
|
||||
layerFilter = overrideLayerFilter;
|
||||
materialPass = overrideMaterialPass;
|
||||
clearColor = overrideClearColor;
|
||||
try
|
||||
{
|
||||
return Render(document, output != null ? output.width : 0, output != null ? output.height : 0);
|
||||
}
|
||||
finally
|
||||
{
|
||||
targetTexture = previousTargetTexture;
|
||||
layerFilter = previousLayerFilter;
|
||||
materialPass = previousMaterialPass;
|
||||
clearColor = previousClearColor;
|
||||
}
|
||||
}
|
||||
|
||||
private Material ResolveMaterial()
|
||||
{
|
||||
if (splatMaterial != null)
|
||||
return splatMaterial;
|
||||
|
||||
if (_ownedSplatMaterial != null)
|
||||
return _ownedSplatMaterial;
|
||||
|
||||
var shader = Shader.Find("Aetheria/Render Splats");
|
||||
if (shader == null)
|
||||
return null;
|
||||
|
||||
_ownedSplatMaterial = new Material(shader)
|
||||
{
|
||||
hideFlags = HideFlags.DontSave
|
||||
};
|
||||
return _ownedSplatMaterial;
|
||||
}
|
||||
|
||||
private RenderTexture EnsureTarget(int overrideWidth, int overrideHeight)
|
||||
{
|
||||
if (targetTexture != null)
|
||||
return targetTexture;
|
||||
|
||||
var targetWidth = math.max(1, overrideWidth > 0 ? overrideWidth : width);
|
||||
var targetHeight = math.max(1, overrideHeight > 0 ? overrideHeight : height);
|
||||
if (_ownedTargetTexture != null &&
|
||||
_ownedTargetTexture.width == targetWidth &&
|
||||
_ownedTargetTexture.height == targetHeight &&
|
||||
_ownedTargetTexture.graphicsFormat == targetFormat)
|
||||
{
|
||||
return _ownedTargetTexture;
|
||||
}
|
||||
|
||||
ReleaseOwnedTarget();
|
||||
var descriptor = new RenderTextureDescriptor(targetWidth, targetHeight)
|
||||
{
|
||||
depthBufferBits = 0,
|
||||
graphicsFormat = targetFormat,
|
||||
msaaSamples = 1,
|
||||
sRGB = false,
|
||||
useMipMap = false,
|
||||
autoGenerateMips = false
|
||||
};
|
||||
_ownedTargetTexture = new RenderTexture(descriptor)
|
||||
{
|
||||
name = "Aetheria Render Splats",
|
||||
filterMode = FilterMode.Bilinear,
|
||||
wrapMode = TextureWrapMode.Clamp
|
||||
};
|
||||
_ownedTargetTexture.Create();
|
||||
return _ownedTargetTexture;
|
||||
}
|
||||
|
||||
private void Clear(RenderTexture output)
|
||||
{
|
||||
_commandBuffer ??= new CommandBuffer { name = "Aetheria Render Splats" };
|
||||
_commandBuffer.Clear();
|
||||
_commandBuffer.SetRenderTarget(output);
|
||||
_commandBuffer.ClearRenderTarget(false, true, clearColor);
|
||||
Graphics.ExecuteCommandBuffer(_commandBuffer);
|
||||
}
|
||||
|
||||
private static Matrix4x4 BuildViewportToClip(AetheriaRuntimeRtsViewportBounds viewport)
|
||||
{
|
||||
viewport ??= new AetheriaRuntimeRtsViewportBounds();
|
||||
var minX = (float)math.min(viewport.MinX, viewport.MaxX);
|
||||
var minY = (float)math.min(viewport.MinY, viewport.MaxY);
|
||||
var maxX = (float)math.max(viewport.MinX, viewport.MaxX);
|
||||
var maxY = (float)math.max(viewport.MinY, viewport.MaxY);
|
||||
var width = math.max(0.0001f, maxX - minX);
|
||||
var height = math.max(0.0001f, maxY - minY);
|
||||
|
||||
return new Matrix4x4(
|
||||
new Vector4(2f / width, 0, 0, 0),
|
||||
new Vector4(0, 2f / height, 0, 0),
|
||||
new Vector4(0, 0, 1, 0),
|
||||
new Vector4(-(maxX + minX) / width, -(maxY + minY) / height, 0, 1));
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
_buffer.Dispose();
|
||||
_commandBuffer?.Release();
|
||||
_commandBuffer = null;
|
||||
DestroyOwnedMaterial();
|
||||
ReleaseOwnedTarget();
|
||||
LastDrawnCount = 0;
|
||||
}
|
||||
|
||||
private void DestroyOwnedMaterial()
|
||||
{
|
||||
if (_ownedSplatMaterial == null)
|
||||
return;
|
||||
|
||||
Destroy(_ownedSplatMaterial);
|
||||
_ownedSplatMaterial = null;
|
||||
}
|
||||
|
||||
private void ReleaseOwnedTarget()
|
||||
{
|
||||
if (_ownedTargetTexture == null)
|
||||
return;
|
||||
|
||||
_ownedTargetTexture.Release();
|
||||
Destroy(_ownedTargetTexture);
|
||||
_ownedTargetTexture = null;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: f5150857a83849f195312d13b8f4d19b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -36,7 +36,6 @@ public sealed class AetheriaUnityPilotFrameAdapter
|
|||
return;
|
||||
}
|
||||
|
||||
var renderSettings = ZoneRenderer?.RenderSettings;
|
||||
TargetPresentation?.Tick(currentEntity, timeSeconds);
|
||||
|
||||
var look = Input.Player.Look.ReadValue<Vector2>();
|
||||
|
|
@ -50,13 +49,14 @@ public sealed class AetheriaUnityPilotFrameAdapter
|
|||
_setViewDirection(viewDirection);
|
||||
PilotCommands.RequestLookDirection(viewDirection);
|
||||
|
||||
if (renderSettings != null)
|
||||
if (ZoneRenderer != null)
|
||||
{
|
||||
var renderSettings = ZoneRenderer.RenderSettings;
|
||||
if (HeatstrokePost != null)
|
||||
HeatstrokePost.weight = (float)renderSettings.Value.NormalizeHeatstrokePost(currentEntity.Heatstroke);
|
||||
HeatstrokePost.weight = (float)renderSettings.NormalizeHeatstrokePost(currentEntity.Heatstroke);
|
||||
if (SevereHeatstrokePost != null)
|
||||
SevereHeatstrokePost.weight =
|
||||
(float)renderSettings.Value.ResolveSevereHeatstrokePostWeight(currentEntity.Heatstroke, timeSeconds);
|
||||
(float)renderSettings.ResolveSevereHeatstrokePostWeight(currentEntity.Heatstroke, timeSeconds);
|
||||
}
|
||||
|
||||
if (currentEntity is Ship)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,164 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using GameCult.Aetheria.State.Verse;
|
||||
using UnityEngine;
|
||||
|
||||
public sealed class AetheriaUnityRenderSplatViewportSource : MonoBehaviour
|
||||
{
|
||||
[SerializeField]
|
||||
private AetheriaRenderSplatRasterizer rasterizer;
|
||||
|
||||
[SerializeField]
|
||||
private AetheriaRenderSplatLayerRenderer layerRenderer;
|
||||
|
||||
[SerializeField]
|
||||
private string layerKey = AetheriaRuntimeRenderSplatLayerKeys.GravityHeight;
|
||||
|
||||
[SerializeField]
|
||||
private Vector2 position;
|
||||
|
||||
[SerializeField]
|
||||
private Vector2 size = new Vector2(1024, 1024);
|
||||
|
||||
[SerializeField]
|
||||
private float refreshIntervalSeconds = 0.5f;
|
||||
|
||||
[SerializeField]
|
||||
private bool renderInLateUpdate = true;
|
||||
|
||||
private AetheriaClient _client;
|
||||
private string _clientStatePath = "";
|
||||
private float _nextRefreshTime;
|
||||
private AetheriaRuntimeRenderSplatsViewportDocument _document;
|
||||
|
||||
public RenderTexture TargetTexture
|
||||
{
|
||||
get
|
||||
{
|
||||
if (layerRenderer != null && layerRenderer.TryGetTexture(layerKey, out var texture))
|
||||
return texture;
|
||||
|
||||
return rasterizer != null ? rasterizer.TargetTexture : null;
|
||||
}
|
||||
}
|
||||
|
||||
private void Reset()
|
||||
{
|
||||
rasterizer = GetComponent<AetheriaRenderSplatRasterizer>();
|
||||
layerRenderer = GetComponent<AetheriaRenderSplatLayerRenderer>();
|
||||
}
|
||||
|
||||
private void LateUpdate()
|
||||
{
|
||||
if (renderInLateUpdate)
|
||||
RenderLatest();
|
||||
}
|
||||
|
||||
public bool RenderLatest()
|
||||
{
|
||||
ResolveRenderers();
|
||||
if (layerRenderer == null && rasterizer == null)
|
||||
return false;
|
||||
|
||||
RefreshDocument(false);
|
||||
if (_document == null)
|
||||
return false;
|
||||
|
||||
if (layerRenderer != null)
|
||||
{
|
||||
layerRenderer.Render(_document, Mathf.RoundToInt(size.x), Mathf.RoundToInt(size.y));
|
||||
return true;
|
||||
}
|
||||
|
||||
rasterizer.Render(_document, Mathf.RoundToInt(size.x), Mathf.RoundToInt(size.y));
|
||||
return true;
|
||||
}
|
||||
|
||||
private void ResolveRenderers()
|
||||
{
|
||||
if (layerRenderer == null)
|
||||
layerRenderer = GetComponent<AetheriaRenderSplatLayerRenderer>();
|
||||
if (rasterizer == null)
|
||||
rasterizer = GetComponent<AetheriaRenderSplatRasterizer>();
|
||||
|
||||
if (layerRenderer == null && rasterizer == null)
|
||||
layerRenderer = gameObject.AddComponent<AetheriaRenderSplatLayerRenderer>();
|
||||
}
|
||||
|
||||
public void SetLayerKey(string value)
|
||||
{
|
||||
layerKey = string.IsNullOrWhiteSpace(value)
|
||||
? AetheriaRuntimeRenderSplatLayerKeys.GravityHeight
|
||||
: value;
|
||||
}
|
||||
|
||||
public void SetViewport(Vector2 center, Vector2 viewportSize)
|
||||
{
|
||||
position = center;
|
||||
size = viewportSize;
|
||||
RefreshDocument(true);
|
||||
}
|
||||
|
||||
private void RefreshDocument(bool force)
|
||||
{
|
||||
if (!force && Time.unscaledTime < _nextRefreshTime)
|
||||
return;
|
||||
|
||||
_nextRefreshTime = Time.unscaledTime + Mathf.Max(0.02f, refreshIntervalSeconds);
|
||||
try
|
||||
{
|
||||
_document = ResolveClient()
|
||||
.RenderSplatsViewportAsync(ResolveViewportBounds())
|
||||
.GetAwaiter()
|
||||
.GetResult();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogWarning($"Failed to read Aetheria render splats from local Verse state: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private AetheriaRuntimeRtsViewportBounds ResolveViewportBounds()
|
||||
{
|
||||
var halfSize = size * 0.5f;
|
||||
return new AetheriaRuntimeRtsViewportBounds
|
||||
{
|
||||
MinX = position.x - halfSize.x,
|
||||
MinY = position.y - halfSize.y,
|
||||
MaxX = position.x + halfSize.x,
|
||||
MaxY = position.y + halfSize.y
|
||||
};
|
||||
}
|
||||
|
||||
private AetheriaClient ResolveClient()
|
||||
{
|
||||
var gameDataDirectory = new DirectoryInfo(Path.Combine(Application.dataPath, "..", "GameData"));
|
||||
var stateBoot = AetheriaRuntimeStateBoot.Inspect(gameDataDirectory);
|
||||
if (_client != null && string.Equals(_clientStatePath, stateBoot.StateFilePath, StringComparison.Ordinal))
|
||||
return _client;
|
||||
|
||||
DisposeClient();
|
||||
_client = AetheriaClient
|
||||
.OpenLocalAsync(
|
||||
gameDataDirectory,
|
||||
"unity-render-splat-viewport",
|
||||
"local",
|
||||
pullOnOpen: true)
|
||||
.GetAwaiter()
|
||||
.GetResult();
|
||||
_clientStatePath = stateBoot.StateFilePath;
|
||||
return _client;
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
DisposeClient();
|
||||
}
|
||||
|
||||
private void DisposeClient()
|
||||
{
|
||||
_client?.Dispose();
|
||||
_client = null;
|
||||
_clientStatePath = "";
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 473b4029da16430aa0c0ef89298070d8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -61,12 +61,12 @@ public sealed class AetheriaUnityTargetPresentation
|
|||
_observedHostileIndicatorTargets.Clear();
|
||||
_observedFriendlyIndicatorTargets.Clear();
|
||||
|
||||
var renderSettings = ZoneRenderer?.RenderSettings;
|
||||
if (currentEntity != null && renderSettings != null)
|
||||
if (currentEntity != null && ZoneRenderer != null)
|
||||
{
|
||||
var renderSettings = ZoneRenderer.RenderSettings;
|
||||
var contacts = ResolveVisibleContacts?.Invoke(
|
||||
currentEntity,
|
||||
renderSettings.Value.TargetDetectionInfoThreshold,
|
||||
renderSettings.TargetDetectionInfoThreshold,
|
||||
true) ?? Array.Empty<AetheriaRuntimeZoneContactRow>();
|
||||
|
||||
foreach (var contact in contacts)
|
||||
|
|
@ -153,10 +153,10 @@ public sealed class AetheriaUnityTargetPresentation
|
|||
float time,
|
||||
bool blinkSpottedTargets)
|
||||
{
|
||||
var renderSettings = ZoneRenderer?.RenderSettings;
|
||||
if (renderSettings == null)
|
||||
if (ZoneRenderer == null)
|
||||
return;
|
||||
|
||||
var renderSettings = ZoneRenderer.RenderSettings;
|
||||
foreach (var indicator in indicators)
|
||||
{
|
||||
indicator.Value.gameObject.SetActive(indicator.Key != observedTarget);
|
||||
|
|
@ -168,9 +168,9 @@ public sealed class AetheriaUnityTargetPresentation
|
|||
}
|
||||
|
||||
var infoGathered = ResolveInfoGathered?.Invoke(indicator.Key, currentEntity) ?? 0f;
|
||||
indicator.Value.Fill.fillAmount = (float)renderSettings.Value.NormalizeDetectionProgress(infoGathered);
|
||||
indicator.Value.Fill.fillAmount = (float)renderSettings.NormalizeDetectionProgress(infoGathered);
|
||||
indicator.Value.Fill.enabled = !blinkSpottedTargets ||
|
||||
renderSettings.Value.ResolveTargetSpottedFillEnabled(infoGathered, time);
|
||||
renderSettings.ResolveTargetSpottedFillEnabled(infoGathered, time);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue