Fix live monitor video and camera rendering
This commit is contained in:
+23
-4
@@ -58,6 +58,21 @@ namespace Gallop.Live.Cutt
|
||||
public bool IsEnabledBlendMode => (((int)attribute) & AttrEnableBlendMode) != 0;
|
||||
}
|
||||
|
||||
// Monitor-camera tracks carry these fields in addition to the shared camera schema.
|
||||
// Names are case-sensitive and must match the Cutt TypeTree.
|
||||
[Serializable]
|
||||
public class LiveTimelineKeyMonitorCameraPositionData : LiveTimelineKeyCameraPositionData
|
||||
{
|
||||
public Vector3 CharaPositionAtStartFrame;
|
||||
public bool IsUseCharaPositionAtPrevKeyStartFrame;
|
||||
public bool IsAttachedToProps;
|
||||
public int PropsIndex;
|
||||
public int PropsAttachNodeIndex;
|
||||
public bool enable;
|
||||
public float fov = 30f;
|
||||
public float roll;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class LiveTimelineKeyMonitorControlDataList : LiveTimelineKeyDataListTemplate<LiveTimelineKeyMonitorControlData> { }
|
||||
|
||||
@@ -68,9 +83,6 @@ namespace Gallop.Live.Cutt
|
||||
public string SafeName => string.IsNullOrWhiteSpace(name) ? "Monitor" : name;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class LiveTimelineKeyMonitorCameraPositionData : LiveTimelineKeyCameraPositionData { }
|
||||
|
||||
[Serializable]
|
||||
public class LiveTimelineKeyMonitorCameraPositionDataList : LiveTimelineKeyDataListTemplate<LiveTimelineKeyMonitorCameraPositionData> { }
|
||||
|
||||
@@ -81,7 +93,14 @@ namespace Gallop.Live.Cutt
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class LiveTimelineKeyMonitorCameraLookAtData : LiveTimelineKeyCameraLookAtData { }
|
||||
public class LiveTimelineKeyMonitorCameraLookAtData : LiveTimelineKeyCameraLookAtData
|
||||
{
|
||||
public Vector3 CharaPositionAtStartFrame;
|
||||
public bool IsUseCharaPositionAtPrevKeyStartFrame;
|
||||
public bool IsAttachedToProps;
|
||||
public int PropsIndex;
|
||||
public int PropsAttachNodeIndex;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class LiveTimelineKeyMonitorCameraLookAtDataList : LiveTimelineKeyDataListTemplate<LiveTimelineKeyMonitorCameraLookAtData> { }
|
||||
|
||||
@@ -1,2 +1,167 @@
|
||||
// Kept intentionally empty so stale Unity-generated project files that referenced the
|
||||
// superseded temporary driver remain buildable until Unity regenerates the project.
|
||||
using UnityEngine;
|
||||
using Gallop.Live.Cutt;
|
||||
|
||||
namespace Gallop.Live
|
||||
{
|
||||
/// <summary>Renders the dedicated MonitorCamera position/look-at tracks into a screen texture.</summary>
|
||||
public sealed class StageMonitorCameraDriver : MonoBehaviour
|
||||
{
|
||||
private Camera _captureCamera;
|
||||
private RenderTexture _texture;
|
||||
private int _lastPositionKeyFrame = int.MinValue;
|
||||
|
||||
public Texture Texture => _texture;
|
||||
public bool IsActive => _captureCamera != null && _captureCamera.enabled;
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
if (_captureCamera != null)
|
||||
Destroy(_captureCamera.gameObject);
|
||||
if (_texture != null)
|
||||
Destroy(_texture);
|
||||
}
|
||||
|
||||
public void UpdateCapture(LiveTimelineControl timeline, float currentFrame, LiveTimelineData data)
|
||||
{
|
||||
if (timeline == null || data == null || data.worksheetList == null)
|
||||
return;
|
||||
|
||||
LiveTimelineKeyMonitorCameraPositionData positionKey = null;
|
||||
LiveTimelineKeyMonitorCameraPositionData nextPositionKey = null;
|
||||
LiveTimelineKeyMonitorCameraLookAtData lookAtKey = null;
|
||||
LiveTimelineKeyMonitorCameraLookAtData nextLookAtKey = null;
|
||||
for (int i = 0; i < data.worksheetList.Count; i++)
|
||||
{
|
||||
LiveTimelineWorkSheet sheet = data.worksheetList[i];
|
||||
if (sheet == null)
|
||||
continue;
|
||||
if (positionKey == null && sheet.monitorCameraPosKeys != null && sheet.monitorCameraPosKeys.Count > 0)
|
||||
FindKeys(sheet.monitorCameraPosKeys[0].keys, currentFrame, out positionKey, out nextPositionKey);
|
||||
if (lookAtKey == null && sheet.monitorCameraLookAtKeys != null && sheet.monitorCameraLookAtKeys.Count > 0)
|
||||
FindKeys(sheet.monitorCameraLookAtKeys[0].keys, currentFrame, out lookAtKey, out nextLookAtKey);
|
||||
if (positionKey != null && lookAtKey != null)
|
||||
break;
|
||||
}
|
||||
|
||||
if (positionKey == null)
|
||||
return;
|
||||
|
||||
if (!positionKey.enable)
|
||||
{
|
||||
if (_captureCamera != null)
|
||||
_captureCamera.enabled = false;
|
||||
return;
|
||||
}
|
||||
|
||||
float positionRatio = GetRatio(positionKey, nextPositionKey, currentFrame);
|
||||
Vector3 position = positionKey.GetValue(timeline);
|
||||
if (nextPositionKey != null)
|
||||
position = Vector3.Lerp(position, nextPositionKey.GetValue(timeline), positionRatio);
|
||||
|
||||
Vector3 lookAt = position + Vector3.forward;
|
||||
if (lookAtKey != null)
|
||||
{
|
||||
lookAt = lookAtKey.GetValue(timeline, position);
|
||||
if (nextLookAtKey != null)
|
||||
lookAt = Vector3.Lerp(lookAt, nextLookAtKey.GetValue(timeline, position),
|
||||
GetRatio(lookAtKey, nextLookAtKey, currentFrame));
|
||||
}
|
||||
|
||||
EnsureCaptureCamera(positionKey);
|
||||
if (_captureCamera == null)
|
||||
return;
|
||||
|
||||
Camera renderTemplate = Camera.main;
|
||||
if (renderTemplate != null && renderTemplate != _captureCamera)
|
||||
{
|
||||
_captureCamera.CopyFrom(renderTemplate);
|
||||
_captureCamera.targetTexture = _texture;
|
||||
_captureCamera.enabled = true;
|
||||
}
|
||||
|
||||
_captureCamera.transform.position = position;
|
||||
if ((lookAt - position).sqrMagnitude > 0.0001f)
|
||||
_captureCamera.transform.rotation = Quaternion.LookRotation(lookAt - position, Vector3.up);
|
||||
float fov = positionKey.fov;
|
||||
if (nextPositionKey != null)
|
||||
fov = Mathf.Lerp(fov, nextPositionKey.fov, positionRatio);
|
||||
float roll = positionKey.roll;
|
||||
if (nextPositionKey != null)
|
||||
roll = Mathf.Lerp(roll, nextPositionKey.roll, positionRatio);
|
||||
_captureCamera.transform.rotation *= Quaternion.AngleAxis(roll, Vector3.forward);
|
||||
_captureCamera.fieldOfView = fov > 0f ? fov : 30f;
|
||||
_captureCamera.nearClipPlane = positionKey.nearClip;
|
||||
_captureCamera.farClipPlane = positionKey.farClip;
|
||||
|
||||
if (_lastPositionKeyFrame != positionKey.frame)
|
||||
{
|
||||
_lastPositionKeyFrame = positionKey.frame;
|
||||
Debug.Log($"[MonitorCamera] key={positionKey.frame}, pos={position}, lookAt={lookAt}, " +
|
||||
$"mask=0x{_captureCamera.cullingMask:X8}, template='{renderTemplate?.name}'");
|
||||
}
|
||||
}
|
||||
|
||||
private void EnsureCaptureCamera(LiveTimelineKeyCameraPositionData positionKey)
|
||||
{
|
||||
if (_captureCamera == null)
|
||||
{
|
||||
GameObject go = new GameObject("MonitorCameraCapture");
|
||||
go.transform.SetParent(transform, false);
|
||||
_captureCamera = go.AddComponent<Camera>();
|
||||
_captureCamera.enabled = true;
|
||||
_captureCamera.depth = -100f;
|
||||
_captureCamera.clearFlags = CameraClearFlags.Skybox;
|
||||
_captureCamera.fieldOfView = 30f;
|
||||
_captureCamera.nearClipPlane = positionKey.nearClip;
|
||||
_captureCamera.farClipPlane = positionKey.farClip;
|
||||
}
|
||||
|
||||
if (_texture == null || !_texture.IsCreated())
|
||||
{
|
||||
if (_texture != null)
|
||||
Destroy(_texture);
|
||||
_texture = new RenderTexture(1024, 576, 24, RenderTextureFormat.ARGB32)
|
||||
{
|
||||
name = "LiveMonitorCameraRT",
|
||||
filterMode = FilterMode.Bilinear,
|
||||
wrapMode = TextureWrapMode.Clamp,
|
||||
useMipMap = false,
|
||||
autoGenerateMips = false
|
||||
};
|
||||
_texture.Create();
|
||||
}
|
||||
|
||||
_captureCamera.targetTexture = _texture;
|
||||
_captureCamera.enabled = true;
|
||||
}
|
||||
|
||||
private static void FindKeys(LiveTimelineKeyMonitorCameraPositionDataList keys,
|
||||
float frame, out LiveTimelineKeyMonitorCameraPositionData current, out LiveTimelineKeyMonitorCameraPositionData next)
|
||||
{
|
||||
current = null;
|
||||
next = null;
|
||||
if (keys == null || keys.Count == 0)
|
||||
return;
|
||||
LiveTimelineKeyIndex index = keys.FindCurrentKey(frame / LiveTimelineControl.kTargetFpsF);
|
||||
current = index?.key as LiveTimelineKeyMonitorCameraPositionData;
|
||||
next = index?.nextKey as LiveTimelineKeyMonitorCameraPositionData;
|
||||
}
|
||||
|
||||
private static void FindKeys(LiveTimelineKeyMonitorCameraLookAtDataList keys,
|
||||
float frame, out LiveTimelineKeyMonitorCameraLookAtData current, out LiveTimelineKeyMonitorCameraLookAtData next)
|
||||
{
|
||||
current = null;
|
||||
next = null;
|
||||
if (keys == null || keys.Count == 0)
|
||||
return;
|
||||
LiveTimelineKeyIndex index = keys.FindCurrentKey(frame / LiveTimelineControl.kTargetFpsF);
|
||||
current = index?.key as LiveTimelineKeyMonitorCameraLookAtData;
|
||||
next = index?.nextKey as LiveTimelineKeyMonitorCameraLookAtData;
|
||||
}
|
||||
|
||||
private static float GetRatio(LiveTimelineKeyWithInterpolate current, LiveTimelineKeyWithInterpolate next, float frame)
|
||||
{
|
||||
return next != null ? LiveTimelineControl.CalculateInterpolationValue(current, next, frame) : 0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,11 @@ namespace Gallop.Live
|
||||
public bool clearFadeTextureWhenUnused = true;
|
||||
public bool applyRenderQueue = true;
|
||||
public bool applyBlendModeProperties = true;
|
||||
public bool autoDiagnosticLog = true;
|
||||
public bool showDiagnosticOverlay = true;
|
||||
public bool suppressMonitorLightOverlay = false;
|
||||
[Range(0f, 1f)] public float monitorLightIntensity = 0.25f;
|
||||
[Range(0.25f, 10f)] public float diagnosticIntervalSeconds = 2f;
|
||||
public string monitorShaderName = "Gallop/3D/Live/Stage/Monitor";
|
||||
|
||||
[Header("着色器属性名称")]
|
||||
@@ -29,7 +34,7 @@ namespace Gallop.Live
|
||||
public string alphaProperty = "_Alpha";
|
||||
public string colorFadeProperty = "_ColorFade";
|
||||
public string baseColorProperty = "_BaseColor";
|
||||
[Range(0f, 1.5f)] public float brightnessMultiplier = 1f;
|
||||
[Range(0f, 1f)] public float brightnessMultiplier = 1f;
|
||||
public string monitorWidthProperty = "_MonitorWidth";
|
||||
public string monitorHeightProperty = "_MonitorHeight";
|
||||
public string crossFadeRateProperty = "_CrossFadeRate";
|
||||
@@ -42,6 +47,7 @@ namespace Gallop.Live
|
||||
{
|
||||
public Renderer renderer;
|
||||
public Material material;
|
||||
public int materialIndex;
|
||||
public string rendererKey;
|
||||
public string materialKey;
|
||||
public string rendererCompact;
|
||||
@@ -52,6 +58,7 @@ namespace Gallop.Live
|
||||
public float baseAlpha = 1f;
|
||||
public Color baseColor = Color.white;
|
||||
public Color baseColorFade = Color.clear;
|
||||
public float baseColorPower = 1f;
|
||||
public bool hasSrcBlendMode;
|
||||
public float baseSrcBlendMode;
|
||||
public bool hasDstBlendMode;
|
||||
@@ -62,7 +69,7 @@ namespace Gallop.Live
|
||||
|
||||
private struct MonitorTextureState
|
||||
{
|
||||
public Texture2D texture;
|
||||
public Texture texture;
|
||||
public Texture2D maskTexture;
|
||||
public int imageIndex;
|
||||
public Vector2 offset;
|
||||
@@ -89,16 +96,23 @@ namespace Gallop.Live
|
||||
public bool hasFadeTexture;
|
||||
public bool useBlendMode;
|
||||
public bool useBaseColor;
|
||||
public bool useMonitorCamera;
|
||||
}
|
||||
|
||||
private LiveTimelineControl _ctl;
|
||||
private StageController _stage;
|
||||
private MonitorUvMovieProvider _provider;
|
||||
private StageMonitorCameraDriver _cameraDriver;
|
||||
private bool _hasBuiltCache;
|
||||
private bool _hasMonitorTimelineData;
|
||||
private bool _providerContextReady;
|
||||
private int _lastPreparedMusicId = -1;
|
||||
private int _lastPreparedStageInstanceId = int.MinValue;
|
||||
private float _lastBrightnessMultiplier = float.NaN;
|
||||
private float _nextDiagnosticTime;
|
||||
private bool _initialDiagnosticDumped;
|
||||
private string _statusText = "Monitor: waiting";
|
||||
private MaterialPropertyBlock _propertyBlock;
|
||||
|
||||
private readonly List<MonitorMaterialBinding> _bindings = new List<MonitorMaterialBinding>(32);
|
||||
private readonly Dictionary<string, List<MonitorMaterialBinding>> _bindingCache =
|
||||
@@ -112,8 +126,19 @@ namespace Gallop.Live
|
||||
private readonly List<MonitorMaterialBinding> _resolveBuffer = new List<MonitorMaterialBinding>(8);
|
||||
private static readonly List<MonitorMaterialBinding> EmptyBindingList = new List<MonitorMaterialBinding>(0);
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
_propertyBlock = new MaterialPropertyBlock();
|
||||
_cameraDriver = GetComponent<StageMonitorCameraDriver>();
|
||||
if (_cameraDriver == null)
|
||||
_cameraDriver = gameObject.AddComponent<StageMonitorCameraDriver>();
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
if (_propertyBlock == null)
|
||||
_propertyBlock = new MaterialPropertyBlock();
|
||||
|
||||
BindIfPossible();
|
||||
if (rebuildCacheOnEnable)
|
||||
RebuildCache();
|
||||
@@ -125,8 +150,32 @@ namespace Gallop.Live
|
||||
ClearCaches();
|
||||
}
|
||||
|
||||
private void OnGUI()
|
||||
{
|
||||
if (!showDiagnosticOverlay || !_hasMonitorTimelineData)
|
||||
return;
|
||||
|
||||
int oldDepth = GUI.depth;
|
||||
GUI.depth = -100;
|
||||
GUI.Label(new Rect(12f, 12f, 760f, 24f), _statusText);
|
||||
GUI.depth = oldDepth;
|
||||
}
|
||||
|
||||
private void LateUpdate()
|
||||
{
|
||||
if (Input.GetKeyDown(KeyCode.F9))
|
||||
DumpDiagnostics();
|
||||
|
||||
float clampedBrightness = Mathf.Clamp01(brightnessMultiplier);
|
||||
if (!Mathf.Approximately(_lastBrightnessMultiplier, clampedBrightness))
|
||||
{
|
||||
brightnessMultiplier = clampedBrightness;
|
||||
_lastBrightnessMultiplier = clampedBrightness;
|
||||
InvalidateAppliedStates();
|
||||
if (verboseLog)
|
||||
Debug.Log($"[MonitorDiag] brightness={brightnessMultiplier:F3}, bindings={_bindings.Count}");
|
||||
}
|
||||
|
||||
if (_ctl == null || _stage == null || _provider == null)
|
||||
BindIfPossible();
|
||||
|
||||
@@ -139,7 +188,11 @@ namespace Gallop.Live
|
||||
if (!_hasBuiltCache)
|
||||
RebuildCache();
|
||||
|
||||
if (_cameraDriver != null)
|
||||
_cameraDriver.UpdateCapture(_ctl, _ctl.currentLiveTime * LiveTimelineControl.kTargetFpsF, _ctl.data);
|
||||
|
||||
ApplyMonitorTimeline();
|
||||
ApplyMonitorLightOverlay();
|
||||
}
|
||||
|
||||
private void BindIfPossible()
|
||||
@@ -206,6 +259,132 @@ namespace Gallop.Live
|
||||
_rebuildAttemptedForMissingBinding.Clear();
|
||||
_resolveBuffer.Clear();
|
||||
_hasBuiltCache = false;
|
||||
_initialDiagnosticDumped = false;
|
||||
_nextDiagnosticTime = 0f;
|
||||
}
|
||||
|
||||
public void SetBrightnessMultiplier(float value)
|
||||
{
|
||||
brightnessMultiplier = Mathf.Clamp01(value);
|
||||
_lastBrightnessMultiplier = brightnessMultiplier;
|
||||
InvalidateAppliedStates();
|
||||
if (verboseLog)
|
||||
Debug.Log($"[MonitorDiag] brightness set to {brightnessMultiplier:F3}, bindings={_bindings.Count}");
|
||||
}
|
||||
|
||||
public void SetMonitorLightIntensity(float value)
|
||||
{
|
||||
monitorLightIntensity = Mathf.Clamp01(value);
|
||||
}
|
||||
|
||||
private void InvalidateAppliedStates()
|
||||
{
|
||||
for (int i = 0; i < _bindings.Count; i++)
|
||||
{
|
||||
if (_bindings[i] != null)
|
||||
_bindings[i].hasAppliedState = false;
|
||||
}
|
||||
}
|
||||
|
||||
public void DumpDiagnostics()
|
||||
{
|
||||
Debug.Log(
|
||||
$"[MonitorDiag] dump brightness={brightnessMultiplier:F3}, bindings={_bindings.Count}, " +
|
||||
$"timeline={_hasMonitorTimelineData}, providerReady={_providerContextReady}, " +
|
||||
$"clips={_provider?.clips?.Count ?? 0}, slots={_provider?.ContextSlotCount ?? 0}");
|
||||
|
||||
int count = Mathf.Min(_bindings.Count, 64);
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
MonitorMaterialBinding binding = _bindings[i];
|
||||
if (binding == null || binding.renderer == null || binding.material == null)
|
||||
continue;
|
||||
|
||||
Material material = binding.material;
|
||||
_propertyBlock.Clear();
|
||||
binding.renderer.GetPropertyBlock(_propertyBlock, binding.materialIndex);
|
||||
|
||||
Texture mainTexture = GetEffectiveTexture(material, _propertyBlock, mainTexProperty);
|
||||
Texture filterTexture = GetEffectiveTexture(material, _propertyBlock, filterTexProperty);
|
||||
Texture fadeTexture = GetEffectiveTexture(material, _propertyBlock, fadeTexProperty);
|
||||
Color materialFade = TryHasProperty(material, colorFadeProperty)
|
||||
? material.GetColor(colorFadeProperty) : Color.clear;
|
||||
Color blockFade = _propertyBlock.HasProperty(Shader.PropertyToID(colorFadeProperty))
|
||||
? _propertyBlock.GetColor(colorFadeProperty) : Color.clear;
|
||||
float materialAlpha = TryHasProperty(material, alphaProperty)
|
||||
? material.GetFloat(alphaProperty) : float.NaN;
|
||||
float blockAlpha = _propertyBlock.HasProperty(Shader.PropertyToID(alphaProperty))
|
||||
? _propertyBlock.GetFloat(alphaProperty) : float.NaN;
|
||||
|
||||
Debug.Log(
|
||||
$"[MonitorDiag] #{i} renderer='{GetHierarchyPath(binding.renderer.transform)}' " +
|
||||
$"slot={binding.materialIndex} enabled={binding.renderer.enabled} active={binding.renderer.gameObject.activeInHierarchy} " +
|
||||
$"material='{material.name}' shader='{material.shader?.name}' queue={material.renderQueue} " +
|
||||
$"mpbEmpty={_propertyBlock.isEmpty} main='{DescribeTexture(mainTexture)}' " +
|
||||
$"mainST={DescribeTextureTransform(material, _propertyBlock, mainTexProperty)} " +
|
||||
$"filter='{DescribeTexture(filterTexture)}' filterST={DescribeTextureTransform(material, _propertyBlock, filterTexProperty)} " +
|
||||
$"fade='{DescribeTexture(fadeTexture)}' materialAlpha={materialAlpha:F3} blockAlpha={blockAlpha:F3} " +
|
||||
$"materialColorFade={materialFade} blockColorFade={blockFade}");
|
||||
|
||||
if (material.shader != null && !string.Equals(material.shader.name, monitorShaderName, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
Debug.Log(
|
||||
$"[MonitorDiag] #{i} overlayProps " +
|
||||
$"srcBlend={GetMaterialFloat(material, srcBlendProperty)} dstBlend={GetMaterialFloat(material, dstBlendProperty)} " +
|
||||
$"srcBlendMode={GetMaterialFloat(material, srcBlendModeProperty)} dstBlendMode={GetMaterialFloat(material, dstBlendModeProperty)} " +
|
||||
$"mulColor0={GetMaterialColor(material, "_MulColor0")} colorPower={GetMaterialFloat(material, "_ColorPower")} " +
|
||||
$"baseColor={GetMaterialColor(material, baseColorProperty)}");
|
||||
}
|
||||
}
|
||||
|
||||
if (_bindings.Count > count)
|
||||
Debug.Log($"[MonitorDiag] dump truncated: {_bindings.Count - count} additional bindings");
|
||||
}
|
||||
|
||||
private static Texture GetEffectiveTexture(Material material, MaterialPropertyBlock block, string propertyName)
|
||||
{
|
||||
int propertyId = Shader.PropertyToID(propertyName);
|
||||
if (block.HasProperty(propertyId))
|
||||
return block.GetTexture(propertyId);
|
||||
return HasTextureProperty(material, propertyName) ? material.GetTexture(propertyName) : null;
|
||||
}
|
||||
|
||||
private static float GetMaterialFloat(Material material, string propertyName)
|
||||
{
|
||||
return TryHasProperty(material, propertyName) ? material.GetFloat(propertyName) : float.NaN;
|
||||
}
|
||||
|
||||
private static Color GetMaterialColor(Material material, string propertyName)
|
||||
{
|
||||
return TryHasProperty(material, propertyName) ? material.GetColor(propertyName) : Color.clear;
|
||||
}
|
||||
|
||||
private static string DescribeTexture(Texture texture)
|
||||
{
|
||||
return texture == null ? "<null>" : $"{texture.name} {texture.width}x{texture.height} {texture.graphicsFormat}";
|
||||
}
|
||||
|
||||
private static string DescribeTextureTransform(Material material, MaterialPropertyBlock block, string textureProperty)
|
||||
{
|
||||
int propertyId = Shader.PropertyToID(textureProperty + "_ST");
|
||||
Vector4 value;
|
||||
if (block.HasProperty(propertyId))
|
||||
value = block.GetVector(propertyId);
|
||||
else
|
||||
{
|
||||
Vector2 scale = material.GetTextureScale(textureProperty);
|
||||
Vector2 offset = material.GetTextureOffset(textureProperty);
|
||||
value = new Vector4(scale.x, scale.y, offset.x, offset.y);
|
||||
}
|
||||
return value.ToString("F5");
|
||||
}
|
||||
|
||||
private static string GetHierarchyPath(Transform target)
|
||||
{
|
||||
string path = target != null ? target.name : "<null>";
|
||||
for (Transform parent = target != null ? target.parent : null; parent != null; parent = parent.parent)
|
||||
path = parent.name + "/" + path;
|
||||
return path;
|
||||
}
|
||||
|
||||
private bool EnsureProviderReady()
|
||||
@@ -312,6 +491,7 @@ namespace Gallop.Live
|
||||
{
|
||||
renderer = renderer,
|
||||
material = material,
|
||||
materialIndex = j,
|
||||
rendererKey = NormalizeName(renderer.name),
|
||||
materialKey = NormalizeName(material.name),
|
||||
rendererCompact = CompactName(renderer.name),
|
||||
@@ -332,6 +512,8 @@ namespace Gallop.Live
|
||||
binding.baseColorFade = material.GetColor(colorFadeProperty);
|
||||
if (TryHasProperty(material, baseColorProperty))
|
||||
binding.baseColor = material.GetColor(baseColorProperty);
|
||||
if (TryHasProperty(material, "_ColorPower"))
|
||||
binding.baseColorPower = material.GetFloat("_ColorPower");
|
||||
|
||||
if (TryHasProperty(material, srcBlendModeProperty))
|
||||
{
|
||||
@@ -376,6 +558,27 @@ namespace Gallop.Live
|
||||
Debug.Log($"[StageMonitorDriver] cache rebuilt: bindings={_bindings.Count}");
|
||||
}
|
||||
|
||||
private void ApplyMonitorLightOverlay()
|
||||
{
|
||||
float intensity = Mathf.Clamp01(brightnessMultiplier) * Mathf.Clamp01(monitorLightIntensity);
|
||||
for (int i = 0; i < _bindings.Count; i++)
|
||||
{
|
||||
MonitorMaterialBinding binding = _bindings[i];
|
||||
if (binding == null || binding.renderer == null || binding.material == null ||
|
||||
!string.Equals(binding.rendererKey, "monitorlight000", StringComparison.OrdinalIgnoreCase))
|
||||
continue;
|
||||
|
||||
binding.renderer.enabled = !suppressMonitorLightOverlay;
|
||||
if (suppressMonitorLightOverlay)
|
||||
continue;
|
||||
|
||||
_propertyBlock.Clear();
|
||||
binding.renderer.GetPropertyBlock(_propertyBlock, binding.materialIndex);
|
||||
TrySetFloat(binding.material, _propertyBlock, "_ColorPower", binding.baseColorPower * intensity);
|
||||
binding.renderer.SetPropertyBlock(_propertyBlock, binding.materialIndex);
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyMonitorTimeline()
|
||||
{
|
||||
if (_provider == null)
|
||||
@@ -484,6 +687,32 @@ namespace Gallop.Live
|
||||
|
||||
for (int j = 0; j < targets.Count; j++)
|
||||
ApplyShaderState(targets[j], state);
|
||||
|
||||
string mode = state.useMonitorCamera
|
||||
? (_cameraDriver != null && _cameraDriver.Texture != null ? "CAMERA" : "CAMERA/UNRESOLVED")
|
||||
: (state.hasMainTexture ? "REC" : "CAMERA/UNRESOLVED");
|
||||
string textureName = state.hasMainTexture && state.main.texture != null
|
||||
? state.main.texture.name : "<none>";
|
||||
_statusText =
|
||||
$"Monitor: {mode} dispID={curKey.dispID} tex={textureName} " +
|
||||
$"targets={targets.Count} brightness={brightnessMultiplier:F2}";
|
||||
|
||||
if (autoDiagnosticLog && Time.unscaledTime >= _nextDiagnosticTime)
|
||||
{
|
||||
_nextDiagnosticTime = Time.unscaledTime + Mathf.Max(0.25f, diagnosticIntervalSeconds);
|
||||
Debug.Log(
|
||||
$"[MonitorDiag] auto mode={mode}, monitor='{bindingName}', dispID={curKey.dispID}, " +
|
||||
$"dispID2={curKey.DispID2}, main='{textureName}', mainST={state.main.scale}/{state.main.offset}, " +
|
||||
$"fade={(state.hasFadeTexture ? state.fade.texture?.name : "<none>")}, " +
|
||||
$"alpha={state.alpha:F3}, colorFade={state.colorFade}, brightness={brightnessMultiplier:F3}, " +
|
||||
$"targets={targets.Count}");
|
||||
|
||||
if (!_initialDiagnosticDumped)
|
||||
{
|
||||
_initialDiagnosticDumped = true;
|
||||
DumpDiagnostics();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -538,6 +767,7 @@ namespace Gallop.Live
|
||||
|
||||
MonitorUvMovieContextSlot primarySlot = ResolvePrimarySlot(monitorData, curKey);
|
||||
MonitorUvMovieContextSlot fadeSlot = ResolveFadeSlot(monitorData, curKey);
|
||||
state.useMonitorCamera = curKey.IsMonitorCameraFlag() && _cameraDriver != null && _cameraDriver.IsActive;
|
||||
|
||||
if (primarySlot != null && primarySlot.clip != null &&
|
||||
TryBuildTextureState(primarySlot, primarySlot.clip, localTime, playbackSpeed, isReversePlay, curKey.playStartOffsetFrame, curKey.LightImageNo, out MonitorTextureState mainTexture))
|
||||
@@ -553,13 +783,21 @@ namespace Gallop.Live
|
||||
state.hasFadeTexture = true;
|
||||
}
|
||||
|
||||
if (state.useMonitorCamera && _cameraDriver != null && _cameraDriver.Texture != null)
|
||||
{
|
||||
state.main.texture = _cameraDriver.Texture;
|
||||
state.main.scale = Vector2.one;
|
||||
state.main.offset = Vector2.zero;
|
||||
state.hasMainTexture = true;
|
||||
state.hasFadeTexture = false;
|
||||
}
|
||||
|
||||
if (assignMaskTextureToFilterTex && state.hasMainTexture)
|
||||
state.filterTexture = state.main.maskTexture;
|
||||
|
||||
// 保持 _Alpha 为材质原本的值
|
||||
// 时间轴的监视器数据没有提供专用的透明度字段;
|
||||
// colorFade 和 BaseColor 会分别传递给对应的着色器参数
|
||||
state.alpha = 0f;
|
||||
// _Alpha only cross-fades _MainTex into _FadeTex. The shader's default
|
||||
// fade texture is white, so keep it at zero when no second clip exists.
|
||||
state.alpha = state.hasFadeTexture ? Mathf.Clamp01(crossFadeRate) : 0f;
|
||||
state.colorFade = colorFade;
|
||||
state.useBaseColor = !IsColorEffectivelyClear(baseColor);
|
||||
state.baseColor = state.useBaseColor ? baseColor : Color.white;
|
||||
@@ -914,36 +1152,25 @@ namespace Gallop.Live
|
||||
private void ApplyShaderState(MonitorMaterialBinding binding, MonitorShaderState state)
|
||||
{
|
||||
Material material = binding.material;
|
||||
if (material == null)
|
||||
Renderer renderer = binding.renderer;
|
||||
if (material == null || renderer == null)
|
||||
return;
|
||||
|
||||
_propertyBlock.Clear();
|
||||
renderer.GetPropertyBlock(_propertyBlock, binding.materialIndex);
|
||||
|
||||
if (state.hasMainTexture && HasTextureProperty(material, mainTexProperty))
|
||||
{
|
||||
if (!binding.hasAppliedState ||
|
||||
binding.appliedState.main.texture != state.main.texture ||
|
||||
!Approximately(binding.appliedState.main.scale, state.main.scale) ||
|
||||
!Approximately(binding.appliedState.main.offset, state.main.offset))
|
||||
{
|
||||
material.SetTexture(mainTexProperty, state.main.texture);
|
||||
material.SetTextureScale(mainTexProperty, state.main.scale);
|
||||
material.SetTextureOffset(mainTexProperty, state.main.offset);
|
||||
}
|
||||
_propertyBlock.SetTexture(mainTexProperty, state.main.texture);
|
||||
SetTextureTransform(_propertyBlock, mainTexProperty, state.main.scale, state.main.offset);
|
||||
}
|
||||
|
||||
if (HasTextureProperty(material, fadeTexProperty))
|
||||
{
|
||||
if (state.hasFadeTexture)
|
||||
{
|
||||
if (!binding.hasAppliedState ||
|
||||
!binding.appliedState.hasFadeTexture ||
|
||||
binding.appliedState.fade.texture != state.fade.texture ||
|
||||
!Approximately(binding.appliedState.fade.scale, state.fade.scale) ||
|
||||
!Approximately(binding.appliedState.fade.offset, state.fade.offset))
|
||||
{
|
||||
material.SetTexture(fadeTexProperty, state.fade.texture);
|
||||
material.SetTextureScale(fadeTexProperty, state.fade.scale);
|
||||
material.SetTextureOffset(fadeTexProperty, state.fade.offset);
|
||||
}
|
||||
_propertyBlock.SetTexture(fadeTexProperty, state.fade.texture);
|
||||
SetTextureTransform(_propertyBlock, fadeTexProperty, state.fade.scale, state.fade.offset);
|
||||
}
|
||||
else if (clearFadeTextureWhenUnused &&
|
||||
(!binding.hasAppliedState || binding.appliedState.hasFadeTexture))
|
||||
@@ -957,38 +1184,21 @@ namespace Gallop.Live
|
||||
if (HasTextureProperty(material, filterTexProperty))
|
||||
{
|
||||
Vector2 filterScale = binding.baseFilterScale * state.filterTexScale;
|
||||
if (assignMaskTextureToFilterTex &&
|
||||
(!binding.hasAppliedState || binding.appliedState.filterTexture != state.filterTexture))
|
||||
material.SetTexture(filterTexProperty, state.filterTexture);
|
||||
|
||||
if (!binding.hasAppliedState ||
|
||||
!Approximately(binding.appliedState.filterTexScale, state.filterTexScale))
|
||||
{
|
||||
material.SetTextureScale(filterTexProperty, filterScale);
|
||||
}
|
||||
|
||||
if (!binding.hasAppliedState)
|
||||
{
|
||||
material.SetTextureOffset(filterTexProperty, binding.baseFilterOffset);
|
||||
}
|
||||
if (assignMaskTextureToFilterTex && state.filterTexture != null)
|
||||
_propertyBlock.SetTexture(filterTexProperty, state.filterTexture);
|
||||
SetTextureTransform(_propertyBlock, filterTexProperty, filterScale, binding.baseFilterOffset);
|
||||
}
|
||||
|
||||
float appliedAlpha = binding.baseAlpha;
|
||||
Color appliedColorFade = ScaleBrightness(state.colorFade);
|
||||
Color appliedBaseColor = ScaleBrightness(state.useBaseColor ? state.baseColor : binding.baseColor);
|
||||
float appliedAlpha = state.alpha;
|
||||
Color appliedColorFade = ApplyBrightnessToColorFade(state.colorFade);
|
||||
Color appliedBaseColor = state.useBaseColor ? state.baseColor : binding.baseColor;
|
||||
|
||||
if (!binding.hasAppliedState || !Approximately(binding.appliedState.alpha, appliedAlpha))
|
||||
TrySetFloat(material, alphaProperty, appliedAlpha);
|
||||
if (!binding.hasAppliedState || !Approximately(binding.appliedState.colorFade, appliedColorFade))
|
||||
TrySetColor(material, colorFadeProperty, appliedColorFade);
|
||||
if (!binding.hasAppliedState || !Approximately(binding.appliedState.baseColor, appliedBaseColor))
|
||||
TrySetColor(material, baseColorProperty, appliedBaseColor);
|
||||
if (!binding.hasAppliedState || !Approximately(binding.appliedState.width, state.width))
|
||||
TrySetFloat(material, monitorWidthProperty, state.width);
|
||||
if (!binding.hasAppliedState || !Approximately(binding.appliedState.height, state.height))
|
||||
TrySetFloat(material, monitorHeightProperty, state.height);
|
||||
if (!binding.hasAppliedState || !Approximately(binding.appliedState.crossFadeRate, state.crossFadeRate))
|
||||
TrySetFloat(material, crossFadeRateProperty, state.crossFadeRate);
|
||||
TrySetFloat(material, _propertyBlock, alphaProperty, appliedAlpha);
|
||||
TrySetColor(material, _propertyBlock, colorFadeProperty, appliedColorFade);
|
||||
TrySetColor(material, _propertyBlock, baseColorProperty, appliedBaseColor);
|
||||
TrySetFloat(material, _propertyBlock, monitorWidthProperty, state.width);
|
||||
TrySetFloat(material, _propertyBlock, monitorHeightProperty, state.height);
|
||||
TrySetFloat(material, _propertyBlock, crossFadeRateProperty, state.crossFadeRate);
|
||||
|
||||
if (applyBlendModeProperties)
|
||||
{
|
||||
@@ -1041,6 +1251,8 @@ namespace Gallop.Live
|
||||
material.renderQueue = state.renderQueue;
|
||||
}
|
||||
|
||||
renderer.SetPropertyBlock(_propertyBlock, binding.materialIndex);
|
||||
|
||||
MonitorShaderState storedState = state;
|
||||
storedState.alpha = appliedAlpha;
|
||||
storedState.colorFade = appliedColorFade;
|
||||
@@ -1239,13 +1451,43 @@ namespace Gallop.Live
|
||||
return true;
|
||||
}
|
||||
|
||||
private Color ScaleBrightness(Color color)
|
||||
private static void TrySetFloat(Material material, MaterialPropertyBlock block, string propertyName, float value)
|
||||
{
|
||||
float multiplier = Mathf.Clamp(brightnessMultiplier, 0f, 1.5f);
|
||||
color.r *= multiplier;
|
||||
color.g *= multiplier;
|
||||
color.b *= multiplier;
|
||||
return color;
|
||||
if (TryHasProperty(material, propertyName))
|
||||
block.SetFloat(propertyName, value);
|
||||
}
|
||||
|
||||
private static void TrySetColor(Material material, MaterialPropertyBlock block, string propertyName, Color value)
|
||||
{
|
||||
if (TryHasProperty(material, propertyName))
|
||||
block.SetColor(propertyName, value);
|
||||
}
|
||||
|
||||
private static void SetTextureTransform(MaterialPropertyBlock block, string textureProperty, Vector2 scale, Vector2 offset)
|
||||
{
|
||||
block.SetVector(textureProperty + "_ST", new Vector4(scale.x, scale.y, offset.x, offset.y));
|
||||
}
|
||||
|
||||
private Color ApplyBrightnessToColorFade(Color colorFade)
|
||||
{
|
||||
float multiplier = Mathf.Clamp01(brightnessMultiplier);
|
||||
if (Mathf.Approximately(multiplier, 1f))
|
||||
return colorFade;
|
||||
|
||||
// Monitor.shader outputs lerp(ColorFade.rgb, screen.rgb, ColorFade.a) * 2.
|
||||
// Re-encode both terms so this scales the full result even when alpha is one.
|
||||
float sourceAlpha = Mathf.Clamp01(colorFade.a);
|
||||
float scaledAlpha = sourceAlpha * multiplier;
|
||||
float remainingWeight = 1f - scaledAlpha;
|
||||
float colorScale = remainingWeight > 0.0001f
|
||||
? multiplier * (1f - sourceAlpha) / remainingWeight
|
||||
: 0f;
|
||||
|
||||
colorFade.r *= colorScale;
|
||||
colorFade.g *= colorScale;
|
||||
colorFade.b *= colorScale;
|
||||
colorFade.a = scaledAlpha;
|
||||
return colorFade;
|
||||
}
|
||||
|
||||
private static bool IsColorEffectivelyClear(Color value)
|
||||
|
||||
@@ -49,6 +49,7 @@ namespace Gallop.Live
|
||||
private float _otherInertScale = 1.1f;
|
||||
private float _otherRigidity = 0.85f;
|
||||
private float _monitorBrightness = 1f;
|
||||
private float _monitorLightIntensity = 0.25f;
|
||||
private bool _showSkirtColliders;
|
||||
private bool _skirtBodyCollisionEnabled = true;
|
||||
|
||||
@@ -143,7 +144,8 @@ namespace Gallop.Live
|
||||
|
||||
GUILayout.Space(5);
|
||||
GUILayout.Label("Live display");
|
||||
changed |= SliderRow("Screen brightness", ref _monitorBrightness, 0f, 1.5f);
|
||||
changed |= SliderRow("Screen brightness", ref _monitorBrightness, 0f, 1f);
|
||||
changed |= SliderRow("Screen glow", ref _monitorLightIntensity, 0f, 1f);
|
||||
|
||||
if (GUILayout.Button("Restore defaults", GUILayout.Width(165)))
|
||||
{
|
||||
@@ -160,6 +162,7 @@ namespace Gallop.Live
|
||||
_otherInertScale = 1.1f;
|
||||
_otherRigidity = 0.85f;
|
||||
_monitorBrightness = 1f;
|
||||
_monitorLightIntensity = 0.25f;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
@@ -181,7 +184,12 @@ namespace Gallop.Live
|
||||
SetSkirtBodyCollisionEnabled(_skirtBodyCollisionEnabled);
|
||||
}
|
||||
|
||||
UmaContainerCharacter firstCharacter = Director.instance.CharaContainerScript[0];
|
||||
Director director = Director.instance;
|
||||
UmaContainerCharacter firstCharacter = director != null &&
|
||||
director.CharaContainerScript != null &&
|
||||
director.CharaContainerScript.Count > 0
|
||||
? director.CharaContainerScript[0]
|
||||
: null;
|
||||
SkirtSurfaceCollisionSolver solver = firstCharacter != null
|
||||
? firstCharacter.GetComponent<SkirtSurfaceCollisionSolver>() : null;
|
||||
GUILayout.Label(solver != null
|
||||
@@ -279,7 +287,10 @@ namespace Gallop.Live
|
||||
}
|
||||
|
||||
foreach (StageMonitorDriver monitorDriver in Object.FindObjectsOfType<StageMonitorDriver>(true))
|
||||
monitorDriver.brightnessMultiplier = _monitorBrightness;
|
||||
{
|
||||
monitorDriver.SetBrightnessMultiplier(_monitorBrightness);
|
||||
monitorDriver.SetMonitorLightIntensity(_monitorLightIntensity);
|
||||
}
|
||||
}
|
||||
|
||||
private void SetSkirtBodyCollisionEnabled(bool enabled)
|
||||
|
||||
Reference in New Issue
Block a user