调整Bloom
This commit is contained in:
@@ -213,7 +213,7 @@ Material:
|
||||
- _Dst: 10
|
||||
- _DstBlend: 0
|
||||
- _DstBlendAlpha: 0
|
||||
- _EdgeValue: 0.28290388
|
||||
- _EdgeValue: 0.9463588
|
||||
- _EnvironmentReflections: 1
|
||||
- _FNLfanxiangkaiguan: 0
|
||||
- _Face: 1
|
||||
@@ -258,7 +258,7 @@ Material:
|
||||
- _Mask_scale: 1
|
||||
- _Metallic: 0
|
||||
- _OcclusionStrength: 1
|
||||
- _Opacity: 0.7170961
|
||||
- _Opacity: 0.0536412
|
||||
- _Parallax: 0.005
|
||||
- _Pass: 0
|
||||
- _QueueOffset: 0
|
||||
|
||||
Binary file not shown.
BIN
Assets/FR2_Cache.asset
LFS
BIN
Assets/FR2_Cache.asset
LFS
Binary file not shown.
Binary file not shown.
180
Assets/Prefabs/GameElements/Track/ObjectTracker.prefab
Normal file
180
Assets/Prefabs/GameElements/Track/ObjectTracker.prefab
Normal file
@@ -0,0 +1,180 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!1 &4179914404395760092
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 4793278966605267409}
|
||||
- component: {fileID: 3144517396823347096}
|
||||
- component: {fileID: 5649858463350041749}
|
||||
m_Layer: 0
|
||||
m_Name: ObjectTracker
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!4 &4793278966605267409
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 4179914404395760092}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!114 &3144517396823347096
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 4179914404395760092}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: acb0592a986cebb4287d41702ab6ea22, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
updateMethod: 0
|
||||
_spline: {fileID: 0}
|
||||
_autoUpdate: 1
|
||||
_rotationModifier:
|
||||
blend: 1
|
||||
useClippedPercent: 0
|
||||
keys: []
|
||||
_offsetModifier:
|
||||
blend: 1
|
||||
useClippedPercent: 0
|
||||
keys: []
|
||||
_colorModifier:
|
||||
blend: 1
|
||||
useClippedPercent: 0
|
||||
keys: []
|
||||
_sizeModifier:
|
||||
blend: 1
|
||||
useClippedPercent: 0
|
||||
keys: []
|
||||
_clipFromSample:
|
||||
position: {x: 0, y: 0, z: 0}
|
||||
up: {x: 0, y: 0, z: 0}
|
||||
forward: {x: 0, y: 0, z: 0}
|
||||
color: {r: 0, g: 0, b: 0, a: 0}
|
||||
size: 0
|
||||
percent: 0
|
||||
_clipToSample:
|
||||
position: {x: 0, y: 0, z: 0}
|
||||
up: {x: 0, y: 0, z: 0}
|
||||
forward: {x: 0, y: 0, z: 0}
|
||||
color: {r: 0, g: 0, b: 0, a: 0}
|
||||
size: 0
|
||||
percent: 0
|
||||
_loopSamples: 0
|
||||
_clipFrom: 0
|
||||
_clipTo: 1
|
||||
animClipFrom: 0
|
||||
animClipTo: 1
|
||||
multithreaded: 0
|
||||
buildOnAwake: 1
|
||||
buildOnEnable: 0
|
||||
objects: []
|
||||
_evaluateOffset: 0
|
||||
_spawnMethod: 0
|
||||
_spawnCount: 0
|
||||
_retainPrefabInstancesInEditor: 1
|
||||
_objectPositioning: 0
|
||||
_iteration: 0
|
||||
_randomSeed: 1
|
||||
_minOffset: {x: 0, y: 0, z: 0}
|
||||
_maxOffset: {x: 0, y: 0, z: 0}
|
||||
_offsetUseWorldCoords: 0
|
||||
_minRotation: {x: 0, y: 0, z: 0}
|
||||
_maxRotation: {x: 0, y: 0, z: 0}
|
||||
_uniformScaleLerp: 1
|
||||
_minScaleMultiplier: {x: 1, y: 1, z: 1}
|
||||
_maxScaleMultiplier: {x: 1, y: 1, z: 1}
|
||||
_shellOffset: 0
|
||||
_applyRotation: 0
|
||||
_rotateByOffset: 0
|
||||
_applyScale: 0
|
||||
_objectMethod: 0
|
||||
delayedSpawn: 0
|
||||
spawnDelay: 0.1
|
||||
lastChildCount: 0
|
||||
lastPointCount: 0
|
||||
spawned: []
|
||||
_useCustomObjectDistance: 0
|
||||
_minObjectDistance: 0
|
||||
_maxObjectDistance: 0
|
||||
_customOffsetRule: {fileID: 0}
|
||||
_customRotationRule: {fileID: 0}
|
||||
_customScaleRule: {fileID: 0}
|
||||
--- !u!114 &5649858463350041749
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 4179914404395760092}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: bb900d6a5df01384481372f1fdeca79f, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: Assembly-CSharp::Ichni.RhythmGame.ObjectTracker
|
||||
serializationData:
|
||||
SerializedFormat: 2
|
||||
SerializedBytes:
|
||||
ReferencedUnityObjects: []
|
||||
SerializedBytesString:
|
||||
Prefab: {fileID: 0}
|
||||
PrefabModificationsReferencedUnityObjects: []
|
||||
PrefabModifications: []
|
||||
SerializationNodes:
|
||||
- Name: elementGuid
|
||||
Entry: 2
|
||||
Data: 00000000000000000000000000000000
|
||||
- Name: submoduleList
|
||||
Entry: 7
|
||||
Data: 0|System.Collections.Generic.List`1[[Ichni.RhythmGame.SubmoduleBase,
|
||||
Assembly-CSharp]], mscorlib
|
||||
- Name:
|
||||
Entry: 12
|
||||
Data: 0
|
||||
- Name:
|
||||
Entry: 13
|
||||
Data:
|
||||
- Name:
|
||||
Entry: 8
|
||||
Data:
|
||||
elementName:
|
||||
tags: []
|
||||
parentElement: {fileID: 0}
|
||||
childElementList: []
|
||||
track: {fileID: 0}
|
||||
objectController: {fileID: 3144517396823347096}
|
||||
objectPrefab: {fileID: 0}
|
||||
themeBundleName:
|
||||
objectName:
|
||||
playTime: 0
|
||||
stopTime: 0
|
||||
spawnCount: 0
|
||||
positionOffsetMin: {x: 0, y: 0}
|
||||
positionOffsetMax: {x: 0, y: 0}
|
||||
customPositionRuleName:
|
||||
applyRotationOffset: 0
|
||||
rotationOffsetMin: {x: 0, y: 0, z: 0}
|
||||
rotationOffsetMax: {x: 0, y: 0, z: 0}
|
||||
customRotationRuleName:
|
||||
applyScaleOffset: 0
|
||||
scaleOffsetMin: {x: 1, y: 1, z: 1}
|
||||
scaleOffsetMax: {x: 1, y: 1, z: 1}
|
||||
customScaleRuleName:
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f0debcd3b25df1546b976a04672d2e6f
|
||||
PrefabImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1501ffe885c2449438946c44a354b79d
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Binary file not shown.
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: af6f1decfdfe6064587ed356fcd11cc8
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,5 @@
|
||||
+V›#*‡x©Ý/›,…²þŽ”¤œ!ý
|
||||
º–dIŸ]íœìâfË<66>þ0a¯± "³l¢
|
||||
®•¸00‰æÎ
|
||||
E£'7“ªÚÛš
|
||||
‚d`Þ¤•AßžsB.œ¼Ô_PbÏAõá!<;‡Û‰ØqŸ1
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c7701c20e06b8aa42ba4ef23f0e03491
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,4 @@
|
||||
NΌS,gA-G)Κ9TT/»>’ωΤ?σ <CF83>L,./<2F>η/Q£qlΜF>‘l$<24>*έΉ<CEAD>kΕ.Ήkξ<6B>F‘=‹Λί <20>Τ“\πόmβ(><3E>!°-sβrv(΄
|
||||
1(:<Μφ~ήόz:ί ξ¬<CEBE> ic‹-3Π Ν<04>KΫΠρ<CEA0>‡<EFBFBD><E280A1>Ο^<έδs
|
||||
~ΦΑU{Ήοΰq•π
|
||||
D\―pp&±FάPηRQ\θκΘ¨ύ*|†U™o‰¥‰ Υ™)όόμΚΖμG<_nγι!Έώ]–N…•UΟμ¦&²„λΙC3!Σ$fφ…®fύ<66>a•3ύΡ!³Φ}ν‘Π.jKΠ?]
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 25c731803add7274989b91013dff8639
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1 @@
|
||||
ΞF―<EFBFBD>Θ8uη<EFBFBD>ήΪτΥή›g¤ §†)RΏ—΅Ώ•Χ•ϋ>+2½1εr¶P„ D·σPΦ‰8IΧΩQ5“V<E2809C>[#<23>‚D<E2809A>½z™kzΞ‰aΰ9'υΦθψ?Β,<2C>cώ<63>G}Ψ<>#fκπ z lόM]DD<44>‡ α²μΧZΓXψjR1ΰήιψ8σΔ0µΟ<C2B5>ς΅ΣV®Α<C2AE>®ϊν³µ<C2B3>μ<EFBFBD><CEBC>³*ύ΄LθM#VτΑν<CE91>1ή}£P„w_@?Ή•O~7&S9<><39>λ(ωθ°<µg£γ;ΠΑΝ>μ„j
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cb76f0f963b6e3746972c79fa7d00eb6
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Binary file not shown.
@@ -1 +1,2 @@
|
||||
à"朤öï““ÌASãÙ<C3A3>ŠI§+;ø6ûÝ,¿?¼"yÛ”ÙXbø‹û3ƒMO<4D>¨úlB¨J˜æiAõ*q&_áŽzyþÉ);ã¬"%15v¤åÆoHÄRõÞ×u«|ma’ƒUÅ]äôˆÖ;žÖŒÍ…S]Å<1E>[A«A,ÞS.ÊâŠÝ<‹¡q2fOÙ¥¡XU™¬ùãR2“*S+ÀSqÃÀàôæOôÍù5|ü_@4Uæï<C3A6>È-[W!ª5† ÷Ž¥ªS¢
|
||||
и┴Щ┌╒}┴cА~щ╙⌡╜а╛┐╣╝уЪoU╦#▌яb1h><╒I≈wЪwИ╩╩╩Иов═з}╟╟▐!М┘ящ╔гhиСШмЯi`Б·:е╞■ь1╝╙@╙X║MГj├V
|
||||
б~и#≥r9└╚qвзОР©▌Ю╖j[{л⌠Ъ┌И╝╞_ФСs▒ь┌⌠Л╬кuz≈╣╓нЛ╠╥гёугЯ√ШKbp╥iй┌;▄бЬЭЛ
|
||||
Binary file not shown.
Binary file not shown.
BIN
Assets/Resources/Chapter0.asset
LFS
BIN
Assets/Resources/Chapter0.asset
LFS
Binary file not shown.
@@ -0,0 +1,78 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Ichni.RhythmGame.Beatmap
|
||||
{
|
||||
public class ObjectTracker_BM : GameElement_BM
|
||||
{
|
||||
public string themeBundleName;
|
||||
public string objectName;
|
||||
|
||||
public float playTime;
|
||||
public float stopTime;
|
||||
|
||||
public int spawnCount;
|
||||
public Vector2 positionOffsetMin;
|
||||
public Vector2 positionOffsetMax;
|
||||
public string customPositionRuleName;
|
||||
|
||||
public bool applyRotationOffset;
|
||||
public Vector3 rotationOffsetMin;
|
||||
public Vector3 rotationOffsetMax;
|
||||
public string customRotationRuleName;
|
||||
|
||||
public bool applyScaleOffset;
|
||||
public Vector3 scaleOffsetMin;
|
||||
public Vector3 scaleOffsetMax;
|
||||
public string customScaleRuleName;
|
||||
|
||||
public ObjectTracker_BM()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public ObjectTracker_BM(string elementName, Guid elementGuid, List<string> tags, GameElement_BM attachedElement,
|
||||
string themeBundleName, string objectName,
|
||||
float playTime, float stopTime,
|
||||
int spawnCount,
|
||||
Vector2 positionOffsetMin, Vector2 positionOffsetMax, string customPositionRuleName,
|
||||
bool applyRotationOffset, Vector3 rotationOffsetMin, Vector3 rotationOffsetMax, string customRotationRuleName,
|
||||
bool applyScaleOffset, Vector3 scaleOffsetMin, Vector3 scaleOffsetMax, string customScaleRuleName)
|
||||
: base(elementName, elementGuid, tags, attachedElement)
|
||||
{
|
||||
this.themeBundleName = themeBundleName;
|
||||
this.objectName = objectName;
|
||||
|
||||
this.playTime = playTime;
|
||||
this.stopTime = stopTime;
|
||||
|
||||
this.spawnCount = spawnCount;
|
||||
this.positionOffsetMin = positionOffsetMin;
|
||||
this.positionOffsetMax = positionOffsetMax;
|
||||
this.customPositionRuleName = customPositionRuleName;
|
||||
|
||||
this.applyRotationOffset = applyRotationOffset;
|
||||
this.rotationOffsetMin = rotationOffsetMin;
|
||||
this.rotationOffsetMax = rotationOffsetMax;
|
||||
this.customRotationRuleName = customRotationRuleName;
|
||||
|
||||
this.applyScaleOffset = applyScaleOffset;
|
||||
this.scaleOffsetMin = scaleOffsetMin;
|
||||
this.scaleOffsetMax = scaleOffsetMax;
|
||||
this.customScaleRuleName = customScaleRuleName;
|
||||
}
|
||||
|
||||
public override void ExecuteBM()
|
||||
{
|
||||
matchedElement = ObjectTracker.GenerateElement(
|
||||
elementName, elementGuid, tags, false,
|
||||
GetElement(attachedElementGuid) as Track,
|
||||
themeBundleName, objectName,
|
||||
spawnCount,
|
||||
positionOffsetMin, positionOffsetMax, customPositionRuleName,
|
||||
applyRotationOffset, rotationOffsetMin, rotationOffsetMax, customRotationRuleName,
|
||||
applyScaleOffset, scaleOffsetMin, scaleOffsetMax, customScaleRuleName);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 70dc1a61f3299c642b9c9775d8446595
|
||||
@@ -57,16 +57,23 @@ namespace Ichni.RhythmGame
|
||||
gameCamera.orthographicSize = orthographicSize;
|
||||
gameCamera.cameraTransform = gameCamera.transform;
|
||||
|
||||
float ratioDifference = UIManager.GetScreenRatio() - UIManager.StandardRatio;
|
||||
if (ratioDifference > 0)
|
||||
float currentAspect = UIManager.GetScreenRatio();
|
||||
float targetAspect = UIManager.StandardRatio;
|
||||
|
||||
if (currentAspect < targetAspect)
|
||||
{
|
||||
//gameCamera.perspectiveOffset = 12.5f * ratioDifference;
|
||||
// 屏幕比较方 (如 4:3 平板),需要增加垂直 FOV 以保持 16:9 标准下的水平绝对视野
|
||||
float hFovRad = 2.0f * Mathf.Atan(Mathf.Tan(perspectiveAngle * Mathf.Deg2Rad / 2.0f) * targetAspect);
|
||||
float newVFovRad = 2.0f * Mathf.Atan(Mathf.Tan(hFovRad / 2.0f) / currentAspect);
|
||||
|
||||
// 设置 perspectiveOffset 以完美补足被裁切的空间
|
||||
gameCamera.perspectiveOffset = (newVFovRad * Mathf.Rad2Deg) - perspectiveAngle;
|
||||
}
|
||||
else
|
||||
{
|
||||
gameCamera.perspectiveOffset = -25f * ratioDifference;
|
||||
// 屏幕比较长 (如 20:9 手机),维持原有垂直 FOV,左右延展屏幕用于渲染环境背景
|
||||
gameCamera.perspectiveOffset = 0f;
|
||||
}
|
||||
|
||||
gameCamera.RefreshFOV();
|
||||
return gameCamera;
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ using UnityEngine;
|
||||
|
||||
namespace Ichni.RhythmGame
|
||||
{
|
||||
public abstract partial class GameElement : SerializedMonoBehaviour, IBaseElement, IComparable<GameElement>
|
||||
public abstract partial class GameElement : SerializedMonoBehaviour, IBaseElement, IComparable<GameElement>, IScheduledElement
|
||||
{
|
||||
#region [属性和标记] Essential & Tracking Info
|
||||
//物体名
|
||||
@@ -145,6 +145,19 @@ namespace Ichni.RhythmGame
|
||||
return HierarchyPriority.CompareTo(other.HierarchyPriority);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region [调度更新] IScheduledElement Implementation
|
||||
/// <summary>
|
||||
/// 由 ElementUpdateScheduler 在对应阶段调用。
|
||||
/// 子类按需重写以实现特定阶段的更新逻辑。
|
||||
/// </summary>
|
||||
public virtual void ScheduledUpdate(UpdatePhase phase, float songTime) { }
|
||||
|
||||
/// <summary>
|
||||
/// 元素是否处于活跃状态。调度器跳过非活跃元素以节省开销。
|
||||
/// </summary>
|
||||
public virtual bool IsScheduledActive => isActiveAndEnabled;
|
||||
#endregion
|
||||
}
|
||||
|
||||
#region [额外交互及存档重写预留] Editor Interaction & Interfaces Overrides
|
||||
|
||||
@@ -1,5 +1,137 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Dreamteck.Splines;
|
||||
using UnityEngine;
|
||||
|
||||
public class ObjectTracker : MonoBehaviour
|
||||
namespace Ichni.RhythmGame
|
||||
{
|
||||
public partial class ObjectTracker : GameElement
|
||||
{
|
||||
#region [运行时缓存数据] Property Caches
|
||||
public Track track;
|
||||
public ObjectController objectController;
|
||||
public GameObject objectPrefab;
|
||||
private List<string> themeBundleList;
|
||||
private List<string> objectNameList;
|
||||
public string themeBundleName;
|
||||
public string objectName;
|
||||
|
||||
public float playTime;
|
||||
public float stopTime;
|
||||
|
||||
public int spawnCount;
|
||||
public Vector2 positionOffsetMin = Vector2.zero;
|
||||
public Vector2 positionOffsetMax = Vector2.zero;
|
||||
public string customPositionRuleName;
|
||||
|
||||
public bool applyRotationOffset = false;
|
||||
public Vector3 rotationOffsetMin = Vector3.zero;
|
||||
public Vector3 rotationOffsetMax = Vector3.zero;
|
||||
public string customRotationRuleName;
|
||||
|
||||
public bool applyScaleOffset = false;
|
||||
public Vector3 scaleOffsetMin = Vector3.one;
|
||||
public Vector3 scaleOffsetMax = Vector3.one;
|
||||
public string customScaleRuleName;
|
||||
#endregion
|
||||
|
||||
#region [生成与初始化] Generation & Initialization
|
||||
public static ObjectTracker GenerateElement(string elementName, Guid id, List<string> tags,
|
||||
bool isFirstGenerated, Track track, string themeBundleName, string objectName, int spawnCount,
|
||||
Vector2 positionOffsetMin, Vector2 positionOffsetMax, string customPositionRuleName,
|
||||
bool applyRotationOffset, Vector3 rotationOffsetMin, Vector3 rotationOffsetMax, string customRotationRuleName,
|
||||
bool applyScaleOffset, Vector3 scaleOffsetMin, Vector3 scaleOffsetMax, string customScaleRuleName)
|
||||
{
|
||||
ObjectTracker objectTracker = Instantiate(GameManager.Instance.basePrefabs.objectTracker, track.transform)
|
||||
.GetComponent<ObjectTracker>();
|
||||
objectTracker.objectPrefab = ThemeBundleManager.instance.GetObject<GameObject>(themeBundleName, objectName);
|
||||
|
||||
objectTracker.objectController.objects = new[] { objectTracker.objectPrefab };
|
||||
objectTracker.Initialize(elementName, id, tags, isFirstGenerated, track);
|
||||
objectTracker.track = track;
|
||||
objectTracker.objectController.spline = track.trackPathSubmodule.path;
|
||||
objectTracker.themeBundleList = ThemeBundleManager.instance.loadedThemeBundleList.ConvertAll(x => x.themeBundleName);
|
||||
objectTracker.objectNameList = new List<string>();
|
||||
objectTracker.themeBundleName = themeBundleName;
|
||||
objectTracker.objectName = objectName;
|
||||
objectTracker.SetSpawnSettings(spawnCount,
|
||||
positionOffsetMin, positionOffsetMax, customPositionRuleName,
|
||||
applyRotationOffset, rotationOffsetMin, rotationOffsetMax, customRotationRuleName,
|
||||
applyScaleOffset, scaleOffsetMin, scaleOffsetMax, customScaleRuleName);
|
||||
|
||||
if (isFirstGenerated) objectTracker.AfterInitialize();
|
||||
return objectTracker;
|
||||
}
|
||||
|
||||
public override void AfterInitialize()
|
||||
{
|
||||
base.AfterInitialize();
|
||||
// 向 ElementUpdateScheduler 注册 Phase.TrackFollower
|
||||
CoreServices.UpdateScheduler.Register(UpdatePhase.TrackFollower, this);
|
||||
}
|
||||
|
||||
public void SetSpawnSettings(int spawnCount,
|
||||
Vector2 positionOffsetMin, Vector2 positionOffsetMax, string customPositionRuleName,
|
||||
bool applyRotationOffset, Vector3 rotationOffsetMin, Vector3 rotationOffsetMax, string customRotationRuleName,
|
||||
bool applyScaleOffset, Vector3 scaleOffsetMin, Vector3 scaleOffsetMax, string customScaleRuleName)
|
||||
{
|
||||
this.spawnCount = spawnCount;
|
||||
this.positionOffsetMin = positionOffsetMin;
|
||||
this.positionOffsetMax = positionOffsetMax;
|
||||
this.customPositionRuleName = customPositionRuleName;
|
||||
this.applyRotationOffset = applyRotationOffset;
|
||||
this.rotationOffsetMin = rotationOffsetMin;
|
||||
this.rotationOffsetMax = rotationOffsetMax;
|
||||
this.customRotationRuleName = customRotationRuleName;
|
||||
this.applyScaleOffset = applyScaleOffset;
|
||||
this.scaleOffsetMin = scaleOffsetMin;
|
||||
this.scaleOffsetMax = scaleOffsetMax;
|
||||
this.customScaleRuleName = customScaleRuleName;
|
||||
|
||||
objectController.spawnCount = spawnCount;
|
||||
objectController.minOffset = positionOffsetMin;
|
||||
objectController.maxOffset = positionOffsetMax;
|
||||
|
||||
objectController.applyRotation = applyRotationOffset;
|
||||
objectController.minRotation = rotationOffsetMin;
|
||||
objectController.maxRotation = rotationOffsetMax;
|
||||
|
||||
objectController.applyScale = applyScaleOffset;
|
||||
objectController.minScaleMultiplier = scaleOffsetMin;
|
||||
objectController.maxScaleMultiplier = scaleOffsetMax;
|
||||
|
||||
objectController.Spawn();
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region [运行时控制] Update
|
||||
/// <summary>
|
||||
/// IScheduledElement 实现:在 Phase.TrackFollower 阶段控制 objectController 的启用/禁用。
|
||||
/// </summary>
|
||||
public override void ScheduledUpdate(UpdatePhase phase, float songTime)
|
||||
{
|
||||
if (phase == UpdatePhase.TrackFollower)
|
||||
{
|
||||
if (playTime > songTime || stopTime < songTime)
|
||||
{
|
||||
if (objectController.enabled)
|
||||
objectController.enabled = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!objectController.enabled)
|
||||
{
|
||||
objectController.enabled = true;
|
||||
objectController.Spawn();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnDelete()
|
||||
{
|
||||
CoreServices.UpdateScheduler.Unregister(UpdatePhase.TrackFollower, this);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ public partial class BasePrefabsCollection : SerializedScriptableObject
|
||||
public GameObject crossTrackPoint;
|
||||
public GameObject trackPercentPoint;
|
||||
public GameObject trackHeadPoint;
|
||||
public GameObject objectTracker;
|
||||
|
||||
[Title("Trail相关")] public GameObject trail;
|
||||
public Material defaultTrailMaterial;
|
||||
|
||||
@@ -44,7 +44,7 @@ namespace Ichni.UI
|
||||
AudioManager.Post(AK.EVENTS.TOUCHTOSTART);
|
||||
|
||||
// 已有登录缓存 → 跳过 LoginPage,直接进入章节选择
|
||||
if (LoginCacheManager.HasValidSession)
|
||||
//if (LoginCacheManager.HasValidSession)
|
||||
{
|
||||
FadeOut();
|
||||
floatingParticles.GetComponent<Renderer>().material.DOColor(Color.clear, "_BaseColor", 0.5f).Play();
|
||||
|
||||
Binary file not shown.
@@ -60,7 +60,10 @@ namespace Echovoid.Runtime.Behavior.Rendering
|
||||
public static readonly int _BloomParams = Shader.PropertyToID("_BloomParams");
|
||||
public static readonly int _BloomTint = Shader.PropertyToID("_BloomTint");
|
||||
public static readonly int _AnimeBloom_BlurRadius = Shader.PropertyToID("_BlurRadius");
|
||||
public static readonly int _AnimeBloom_KernelScale = Shader.PropertyToID("_KernelScale");
|
||||
public static readonly int _BloomTex = Shader.PropertyToID("_BloomTex");
|
||||
public static readonly int _SourceTexLowMip = Shader.PropertyToID("_SourceTexLowMip");
|
||||
public static readonly int _BloomScatterParams = Shader.PropertyToID("_BloomScatterParams");
|
||||
|
||||
// --- Anime ACES ---
|
||||
public static readonly int _TonemapParams = Shader.PropertyToID("_TonemapParams");
|
||||
|
||||
@@ -9,118 +9,170 @@ Shader "SLS/Postprocessing/AnimeBloom"
|
||||
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
|
||||
#include "Packages/com.unity.render-pipelines.core/Runtime/Utilities/Blit.hlsl"
|
||||
|
||||
// --- 参数定义 ---
|
||||
float4 _BloomParams; // x: Intensity, y: Threshold, z: SoftKnee, w: Clamp
|
||||
float4 _BloomTint; // 泛光染色
|
||||
float _BlurRadius; // 模糊扩散半径 (控制光晕大小的关键)
|
||||
// =====================================================
|
||||
// 参数定义(与 Unity 原生 Bloom.shader 对齐)
|
||||
// =====================================================
|
||||
// _BloomScatterParams:
|
||||
// x: scatter (已经过 C# 侧 Lerp(0.05, 0.95, userValue) 映射)
|
||||
// y: clamp (最大亮度限制,防萤火虫)
|
||||
// z: threshold (线性空间,C# 侧已做 GammaToLinear)
|
||||
// w: thresholdKnee (= threshold * 0.5f,硬编码 soft knee)
|
||||
float4 _BloomScatterParams;
|
||||
#define Scatter _BloomScatterParams.x
|
||||
#define ClampMax _BloomScatterParams.y
|
||||
#define Threshold _BloomScatterParams.z
|
||||
#define ThresholdKnee _BloomScatterParams.w
|
||||
|
||||
// 纹理
|
||||
// Composite 阶段参数:x=intensity, y/z/w=tint.rgb (已归一化亮度)
|
||||
float4 _BloomParams;
|
||||
float4 _BloomTint; // 保留兼容,不再使用
|
||||
|
||||
float _KernelScale; // 采样跨度放大乘数,默认 1.0
|
||||
|
||||
// SourceTexLowMip: upsample 时的"低频大光晕"纹理 (lowMip)
|
||||
TEXTURE2D(_SourceTexLowMip);
|
||||
SAMPLER(sampler_SourceTexLowMip);
|
||||
|
||||
// 最终 bloom 结果纹理(传入 Composite Pass)
|
||||
TEXTURE2D(_BloomTex);
|
||||
SAMPLER(sampler_BloomTex);
|
||||
|
||||
// --- 辅助函数:Prefilter (提取高亮) ---
|
||||
half3 Prefilter(half3 color)
|
||||
// =====================================================
|
||||
// HDR 编解码(与 Unity 原生 Bloom.shader 完全一致)
|
||||
// 在线性工作流下 encode/decode 是 no-op,但写清楚以防 gamma 空间项目
|
||||
// =====================================================
|
||||
half4 EncodeHDR(half3 color)
|
||||
{
|
||||
float threshold = _BloomParams.y;
|
||||
float softKnee = _BloomParams.z;
|
||||
float clampVal = _BloomParams.w;
|
||||
|
||||
// 1. 限制最大亮度 (防闪烁/萤火虫噪点)
|
||||
color = min(color, clampVal);
|
||||
|
||||
// 2. 阈值计算 (使用 Soft Knee 曲线让过渡更自然)
|
||||
// 标准公式:(Brightness - Threshold) / max(Brightness, 0.0001)
|
||||
// 这里使用一个更平滑的曲线版本,防止高光边缘切变太硬
|
||||
float brightness = Max3(color.r, color.g, color.b);
|
||||
float soft = brightness - threshold + softKnee;
|
||||
soft = clamp(soft, 0, 2 * softKnee);
|
||||
soft = soft * soft / (4 * softKnee + 1e-4);
|
||||
|
||||
float contribution = max(soft, brightness - threshold);
|
||||
contribution /= max(brightness, 1e-4);
|
||||
|
||||
return color * contribution;
|
||||
}
|
||||
|
||||
// --- Pass 0: Prefilter ---
|
||||
half4 FragPrefilter(Varyings input) : SV_Target
|
||||
{
|
||||
// 采样原图
|
||||
half4 color = SAMPLE_TEXTURE2D_X(_BlitTexture, sampler_LinearClamp, input.texcoord);
|
||||
// 提取高亮
|
||||
half3 bloom = Prefilter(color.rgb);
|
||||
return half4(bloom, 1.0);
|
||||
}
|
||||
|
||||
// --- Pass 1: Downsample (Kawase 4-Tap) ---
|
||||
// 降采样:取 4 个对角像素的平均值,范围随分辨率降低而扩大
|
||||
half4 FragDownsample(Varyings input) : SV_Target
|
||||
{
|
||||
float2 uv = input.texcoord;
|
||||
float4 texelSize = _BlitTexture_TexelSize;
|
||||
|
||||
// 原生的 Kawase Offset 是 1.0(即 0.5 个对角像素距离),
|
||||
// 这里加入 _BlurRadius 按比例扩大步幅,能够使用 3 次迭代跑出原版 6 次的扩散面积
|
||||
float spreadOffset = 1.0 + _BlurRadius * 0.5;
|
||||
float2 offset = texelSize.xy * spreadOffset;
|
||||
|
||||
half3 c0 = SAMPLE_TEXTURE2D_X(_BlitTexture, sampler_LinearClamp, uv - offset).rgb;
|
||||
half3 c1 = SAMPLE_TEXTURE2D_X(_BlitTexture, sampler_LinearClamp, uv + float2(offset.x, -offset.y)).rgb;
|
||||
half3 c2 = SAMPLE_TEXTURE2D_X(_BlitTexture, sampler_LinearClamp, uv - float2(offset.x, -offset.y)).rgb;
|
||||
half3 c3 = SAMPLE_TEXTURE2D_X(_BlitTexture, sampler_LinearClamp, uv + offset).rgb;
|
||||
|
||||
half3 color = (c0 + c1 + c2 + c3) * 0.25;
|
||||
#if UNITY_COLORSPACE_GAMMA
|
||||
color = sqrt(color);
|
||||
#endif
|
||||
return half4(color, 1.0);
|
||||
}
|
||||
|
||||
TEXTURE2D(_BloomMipDown);
|
||||
SAMPLER(sampler_BloomMipDown);
|
||||
|
||||
// --- Pass 2: Upsample (Kawase 4-Tap + Scatter) ---
|
||||
half4 FragUpsample(Varyings input) : SV_Target
|
||||
half3 DecodeHDR(half4 data)
|
||||
{
|
||||
float2 uv = input.texcoord;
|
||||
float4 texelSize = _BlitTexture_TexelSize;
|
||||
|
||||
// 原生的 Kawase 升采样偏移是 0.5。
|
||||
// 配合宽幅降采样,这里稍微放大一点点就可以获得非常柔顺的大面积泛光
|
||||
float spreadOffset = 0.5 + _BlurRadius * 0.2;
|
||||
float2 offset = texelSize.xy * spreadOffset;
|
||||
|
||||
// 4-Tap 从更低分辨率纹理 (Up[i+1]) 采样外围光晕
|
||||
half3 c0 = SAMPLE_TEXTURE2D_X(_BlitTexture, sampler_LinearClamp, uv - offset * float2(1, 1)).rgb;
|
||||
half3 c1 = SAMPLE_TEXTURE2D_X(_BlitTexture, sampler_LinearClamp, uv + offset * float2(1, -1)).rgb;
|
||||
half3 c2 = SAMPLE_TEXTURE2D_X(_BlitTexture, sampler_LinearClamp, uv - offset * float2(1, -1)).rgb;
|
||||
half3 c3 = SAMPLE_TEXTURE2D_X(_BlitTexture, sampler_LinearClamp, uv + offset * float2(1, 1)).rgb;
|
||||
half3 lowRes = (c0 + c1 + c2 + c3) * 0.25;
|
||||
|
||||
// 采样当前层级的高清纹理 (Down[i])
|
||||
half3 highRes = SAMPLE_TEXTURE2D_X(_BloomMipDown, sampler_LinearClamp, uv).rgb;
|
||||
|
||||
// 【完全复刻 Unity 原生逻辑】: highRes + lowRes * scatter
|
||||
// 这既保证了光晕向外漫射(低迭代扩散广),又保证了核心亮区的能量守恒!
|
||||
float scatter = saturate(_BlurRadius);
|
||||
half3 bloom = highRes + lowRes * scatter;
|
||||
|
||||
return half4(bloom, 1.0);
|
||||
half3 color = data.xyz;
|
||||
#if UNITY_COLORSPACE_GAMMA
|
||||
color *= color;
|
||||
#endif
|
||||
return color;
|
||||
}
|
||||
|
||||
// --- Pass 3: Composite (最终合成) ---
|
||||
// =====================================================
|
||||
// Pass 0: Prefilter — 提取超过阈值的高亮像素
|
||||
// 与原生完全一致的 soft knee 公式
|
||||
// =====================================================
|
||||
half4 FragPrefilter(Varyings input) : SV_Target
|
||||
{
|
||||
float2 uv = input.texcoord;
|
||||
half3 color = DecodeHDR(SAMPLE_TEXTURE2D_X(_BlitTexture, sampler_LinearClamp, uv));
|
||||
|
||||
// 1. 亮度钳制(防止极亮萤火虫像素跳变)
|
||||
color = min(color, ClampMax);
|
||||
|
||||
// 2. Soft Knee 阈值(与 Unity 原生公式完全一致)
|
||||
half brightness = Max3(color.r, color.g, color.b);
|
||||
half softness = clamp(brightness - Threshold + ThresholdKnee, 0.0, 2.0 * ThresholdKnee);
|
||||
softness = (softness * softness) / (4.0 * ThresholdKnee + 1e-4);
|
||||
half multiplier = max(brightness - Threshold, softness) / max(brightness, 1e-4);
|
||||
color *= multiplier;
|
||||
|
||||
// 3. 防止 NaN 传播(负值在 EncodeHDR sqrt 时会产生 NaN)
|
||||
color = max(color, 0);
|
||||
|
||||
return EncodeHDR(color);
|
||||
}
|
||||
|
||||
// =====================================================
|
||||
// Pass 1: Dual Kawase Downsample
|
||||
// 公式:1/8 * (center*4 + 4corners)
|
||||
// 采样次数:5次(相当于 3×3 box + 双线性权重,效果非常柔和)
|
||||
// 与 Unity 原生 Bloom.shader FragDualDownsample 完全一致
|
||||
// =====================================================
|
||||
half4 FragDualDownsample(Varyings input) : SV_Target
|
||||
{
|
||||
float2 uv = input.texcoord;
|
||||
// 乘以 _KernelScale,在极低迭代次数下强制向外大跨步拉扯光晕
|
||||
float2 ts = _BlitTexture_TexelSize.xy * _KernelScale;
|
||||
|
||||
// 中心点(权重 4)
|
||||
half3 c0 = DecodeHDR(SAMPLE_TEXTURE2D_X(_BlitTexture, sampler_LinearClamp, uv));
|
||||
|
||||
// 4 个对角偏移各 0.5 像素(恰好落在 4 像素的双线性插值中心)
|
||||
half3 c1 = DecodeHDR(SAMPLE_TEXTURE2D_X(_BlitTexture, sampler_LinearClamp, uv + float2( 0.5, 0.5) * ts));
|
||||
half3 c2 = DecodeHDR(SAMPLE_TEXTURE2D_X(_BlitTexture, sampler_LinearClamp, uv + float2(-0.5, 0.5) * ts));
|
||||
half3 c3 = DecodeHDR(SAMPLE_TEXTURE2D_X(_BlitTexture, sampler_LinearClamp, uv + float2(-0.5, -0.5) * ts));
|
||||
half3 c4 = DecodeHDR(SAMPLE_TEXTURE2D_X(_BlitTexture, sampler_LinearClamp, uv + float2( 0.5, -0.5) * ts));
|
||||
|
||||
// 加权平均:(c0*4 + c1+c2+c3+c4) / 8
|
||||
half3 color = (1.0 / 8.0) * (c0 * 4.0 + c1 + c2 + c3 + c4);
|
||||
return EncodeHDR(color);
|
||||
}
|
||||
|
||||
// =====================================================
|
||||
// Pass 2: Dual Kawase Upsample + Energy-Conserving Lerp
|
||||
//
|
||||
// 关键核心:与 Unity 原生 FragUpsample 一致的混合公式
|
||||
// result = lerp(highMip, lowMip, Scatter)
|
||||
//
|
||||
// highMip (_BlitTexture) = 当前层的降采样纹理 Down[i](高分辨率,细节)
|
||||
// lowMip (_SourceTexLowMip) = 上一层的升采样结果 Up[i+1](低分辨率,扩散光晕)
|
||||
//
|
||||
// lerp 是能量守恒的:总亮度 = (1-s)*high + s*low,永远 ≤ max(high,low)
|
||||
// 这是防止"死白"的数学保证。
|
||||
// =====================================================
|
||||
half4 FragDualUpsample(Varyings input) : SV_Target
|
||||
{
|
||||
float2 uv = input.texcoord;
|
||||
// 同样乘以 _KernelScale
|
||||
float2 ts = _BlitTexture_TexelSize.xy * _KernelScale;
|
||||
|
||||
// Dual Kawase 8-tap 升采样:4 对角 + 4 正交
|
||||
// 正交距离 = 1.0 texel,对角距离 = 0.5 texel(与原生完全一致)
|
||||
half3 c1 = DecodeHDR(SAMPLE_TEXTURE2D_X(_BlitTexture, sampler_LinearClamp, uv + float2( 0.5, 0.5) * ts));
|
||||
half3 c2 = DecodeHDR(SAMPLE_TEXTURE2D_X(_BlitTexture, sampler_LinearClamp, uv + float2(-0.5, 0.5) * ts));
|
||||
half3 c3 = DecodeHDR(SAMPLE_TEXTURE2D_X(_BlitTexture, sampler_LinearClamp, uv + float2(-0.5, -0.5) * ts));
|
||||
half3 c4 = DecodeHDR(SAMPLE_TEXTURE2D_X(_BlitTexture, sampler_LinearClamp, uv + float2( 0.5, -0.5) * ts));
|
||||
|
||||
half3 c5 = DecodeHDR(SAMPLE_TEXTURE2D_X(_BlitTexture, sampler_LinearClamp, uv + float2(-1.0, 0.0) * ts));
|
||||
half3 c6 = DecodeHDR(SAMPLE_TEXTURE2D_X(_BlitTexture, sampler_LinearClamp, uv + float2( 1.0, 0.0) * ts));
|
||||
half3 c7 = DecodeHDR(SAMPLE_TEXTURE2D_X(_BlitTexture, sampler_LinearClamp, uv + float2( 0.0, 1.0) * ts));
|
||||
half3 c8 = DecodeHDR(SAMPLE_TEXTURE2D_X(_BlitTexture, sampler_LinearClamp, uv + float2( 0.0, -1.0) * ts));
|
||||
|
||||
// 加权平均 (4角×2 + 4正交×1) / 12,产生柔和的钟形分布
|
||||
half3 highMip = (1.0 / 12.0) * ((c1 + c2 + c3 + c4) * 2.0 + c5 + c6 + c7 + c8);
|
||||
|
||||
// lowMip = 来自更低分辨率层(Up[i+1])的扩散光晕,双线性采样
|
||||
half3 lowMip = DecodeHDR(SAMPLE_TEXTURE2D(_SourceTexLowMip, sampler_LinearClamp, uv));
|
||||
|
||||
// 【核心公式,与原生完全一致】
|
||||
// Scatter 控制光晕扩散程度:0=只有当前层细节,1=完全使用模糊层
|
||||
// 始终能量守恒,绝对不会产生死白
|
||||
half3 result = lerp(highMip, lowMip, Scatter);
|
||||
return EncodeHDR(result);
|
||||
}
|
||||
|
||||
// =====================================================
|
||||
// Pass 3: Composite — 将最终 bloom 叠加回原始画面
|
||||
// _BloomParams.x = intensity
|
||||
// _BloomParams.yzw = tint.rgb (亮度已归一化,仅携带色相/饱和度)
|
||||
// =====================================================
|
||||
half4 FragComposite(Varyings input) : SV_Target
|
||||
{
|
||||
float2 uv = input.texcoord;
|
||||
|
||||
// 原始画面
|
||||
|
||||
// 原始 HDR 画面
|
||||
half4 baseColor = SAMPLE_TEXTURE2D_X(_BlitTexture, sampler_LinearClamp, uv);
|
||||
|
||||
// 泛光结果 (经过多次升采样后的最终纹理)
|
||||
half3 bloom = SAMPLE_TEXTURE2D(_BloomTex, sampler_BloomTex, uv).rgb;
|
||||
|
||||
// 应用强度和染色
|
||||
bloom *= _BloomParams.x * _BloomTint.rgb;
|
||||
// Bloom 纹理(已经过多轮 Dual Kawase 升降采样)
|
||||
half3 bloom = DecodeHDR(SAMPLE_TEXTURE2D(_BloomTex, sampler_BloomTex, uv));
|
||||
|
||||
// 叠加 (Additive)
|
||||
// 也可以尝试 Screen 混合模式让光变得更柔和,但 Additive 最符合物理发光
|
||||
// 应用强度和染色(_BloomParams.yzw 是亮度归一化的 tint)
|
||||
float intensity = _BloomParams.x;
|
||||
half3 tint = _BloomParams.yzw;
|
||||
bloom *= intensity * tint;
|
||||
|
||||
// Additive 合并(物理正确的发光叠加)
|
||||
return half4(baseColor.rgb + bloom, baseColor.a);
|
||||
}
|
||||
|
||||
@@ -129,9 +181,9 @@ Shader "SLS/Postprocessing/AnimeBloom"
|
||||
SubShader
|
||||
{
|
||||
Tags { "RenderType"="Opaque" "RenderPipeline"="UniversalPipeline" }
|
||||
ZWrite Off Cull Off
|
||||
ZWrite Off Cull Off ZTest Always
|
||||
|
||||
// 0: Prefilter
|
||||
// Pass 0: Prefilter
|
||||
Pass
|
||||
{
|
||||
Name "Bloom Prefilter"
|
||||
@@ -141,28 +193,27 @@ Shader "SLS/Postprocessing/AnimeBloom"
|
||||
ENDHLSL
|
||||
}
|
||||
|
||||
// 1: Downsample
|
||||
// Pass 1: Dual Kawase Downsample
|
||||
Pass
|
||||
{
|
||||
Name "Bloom Downsample"
|
||||
Name "Bloom Dual Downsample"
|
||||
HLSLPROGRAM
|
||||
#pragma vertex Vert
|
||||
#pragma fragment FragDownsample
|
||||
#pragma fragment FragDualDownsample
|
||||
ENDHLSL
|
||||
}
|
||||
|
||||
// 2: Upsample
|
||||
// Pass 2: Dual Kawase Upsample + lerp(high, low, scatter)
|
||||
Pass
|
||||
{
|
||||
Name "Bloom Upsample"
|
||||
// 已在内部 Lerp,无需外部 Additive Blend
|
||||
Name "Bloom Dual Upsample"
|
||||
HLSLPROGRAM
|
||||
#pragma vertex Vert
|
||||
#pragma fragment FragUpsample
|
||||
#pragma fragment FragDualUpsample
|
||||
ENDHLSL
|
||||
}
|
||||
|
||||
// 3: Composite
|
||||
// Pass 3: Composite
|
||||
Pass
|
||||
{
|
||||
Name "Bloom Composite"
|
||||
|
||||
@@ -8,39 +8,50 @@ namespace SLSUtilities.Rendering.PostProcessing
|
||||
[System.Serializable, VolumeComponentMenu("SLS/Postprocessing/Anime Bloom")]
|
||||
public class AnimeBloom : ScriptablePostProcessorVolume
|
||||
{
|
||||
// 【核心修改】:放在 ToneMapping 之前执行(BeforePostProcess),配合 ACES 才能彻底解决“高光泛白”问题
|
||||
// BeforePostProcess: 在 ToneMapping 之前运行,确保 HDR bloom 颜色不被截断
|
||||
// 这与 Unity 原生 Bloom 的执行阶段完全一致
|
||||
public override CustomPostProcessInjectionPoint InjectionPoint => CustomPostProcessInjectionPoint.BeforePostProcess;
|
||||
public override int OrderInInjectionPoint => 5; // 放在 Vignette 之前
|
||||
public override int OrderInInjectionPoint => 5;
|
||||
|
||||
[Header("Glow Settings")]
|
||||
[Tooltip("泛光强度。值越大越亮。")]
|
||||
public ClampedFloatParameter intensity = new(0f, 0f, 10f);
|
||||
public ClampedFloatParameter intensity = new(1f, 0f, 10f);
|
||||
|
||||
[Tooltip("阈值。亮度超过此值的像素才会发光。\n关键:设为 1.1 可以过滤掉白墙(1.0),只让灯光发光。")]
|
||||
public MinFloatParameter threshold = new(1.1f, 0f); // 默认设为 1.1
|
||||
// 注意:threshold 在 Gamma 空间中设置,C# 侧会转为 Linear。
|
||||
// 0.9 在 gamma 空间 ≈ 0.79 在 linear 空间,这与 Unity 原生默认行为完全一致。
|
||||
public MinFloatParameter threshold = new(0.9f, 0f);
|
||||
|
||||
[Tooltip("柔膝 (Soft Knee)。让阈值过渡更平滑,避免高光边缘有硬切痕迹。")]
|
||||
public ClampedFloatParameter softKnee = new(0.5f, 0f, 1f);
|
||||
[Header("Scatter")]
|
||||
// scatter 在 [0,1] 范围内由用户设置。
|
||||
// C# 侧映射:Mathf.Lerp(0.05f, 0.95f, scatter.value),与 Unity 原生完全一致。
|
||||
// 低值 = 光晕聚拢,高值 = 光晕向外大范围扩散。
|
||||
public ClampedFloatParameter scatter = new(0.7f, 0f, 1f);
|
||||
|
||||
[Tooltip("最大亮度钳制。防止极亮像素(如太阳)产生乱跳的噪点。")]
|
||||
public MinFloatParameter clamp = new(65472f, 1f); // 默认很大,基本不限制
|
||||
public MinFloatParameter clamp = new(65472f, 1f);
|
||||
|
||||
[Header("Anime Style")]
|
||||
[Tooltip("扩散半径。稍微增加扩展范围来弥补低迭代带来的发光不足。")]
|
||||
public ClampedFloatParameter scatter = new(0.85f, 0f, 5f); // 推荐增量以适应低迭代
|
||||
[Header("Iterations")]
|
||||
// 迭代次数:Dual Kawase 每层只需 1 Pass,性能远低于原生双趟高斯。
|
||||
// 4 次迭代 Dual Kawase ≈ 原生 6 次双趟高斯的扩散范围,但 Pass 数只有其 1/3。
|
||||
public ClampedIntParameter diffusion = new(4, 1, 8);
|
||||
|
||||
[Tooltip("迭代次数。针对移动端带宽优化,建议控制在 2~3 次。")]
|
||||
public ClampedIntParameter diffusion = new(3, 1, 4); // 移动端性能优化,最高4次
|
||||
[Header("Optimization (Mobile)")]
|
||||
[Tooltip("初始降采样倍率(首趟大幅压缩分辨率)。\n1=原生半分辨率(1/2);2=1/4分辨率;3=1/8分辨率。\n调大此值可用极低的迭代次数跑出巨大且柔和的光晕,大幅节省 GPU 带宽。")]
|
||||
public ClampedIntParameter initialDownscaleShift = new(1, 1, 3);
|
||||
|
||||
[Tooltip("采样偏移跨度放大(Kernel Scale)。\n1.0=原生标准跨度。适度放大(如1.2-1.5)可拉扯光晕扩散范围,但过大会产生轻微十字星/方格马赛克。")]
|
||||
public ClampedFloatParameter kernelScale = new(1.0f, 0.5f, 3.0f);
|
||||
|
||||
[Tooltip("泛光染色。可以做粉色霓虹、蓝色科技光等效果。")]
|
||||
[Header("Tint")]
|
||||
// tint 染色:C# 侧会归一化亮度(只保留色相/饱和度),与原生行为一致。
|
||||
// 这样调整 tint 颜色不会意外改变 bloom 总亮度。
|
||||
public ColorParameter tint = new(Color.white, true, true, true);
|
||||
|
||||
// 内部使用的 RT 数组
|
||||
private RTHandle[] _bloomPyramidUp;
|
||||
private RTHandle[] _bloomPyramidDown;
|
||||
private const int k_MaxPyramidSize = 6; // 减少不必要的最大数组长度
|
||||
// RT 数组(仅 Down 金字塔 + Up 金字塔,数量与 diffusion 对齐)
|
||||
private RTHandle[] _bloomMipDown;
|
||||
private RTHandle[] _bloomMipUp;
|
||||
private const int k_MaxMips = 8;
|
||||
|
||||
public override string GetShaderName() => "SLS/Postprocessing/AnimeBloom";
|
||||
public override bool IsActive() => intensity.value > 0f;
|
||||
|
||||
public override void Render(CommandBuffer cmd, ref RenderingData renderingData, RTHandle source, RTHandle destination)
|
||||
{
|
||||
@@ -50,86 +61,123 @@ namespace SLSUtilities.Rendering.PostProcessing
|
||||
desc.msaaSamples = 1;
|
||||
desc.depthBufferBits = 0;
|
||||
|
||||
// 1. 设置参数
|
||||
Vector4 bloomParams = new Vector4(intensity.value, threshold.value, softKnee.value, clamp.value);
|
||||
material.SetVector(InternalShaderHelpers.ID._BloomParams, bloomParams);
|
||||
material.SetVector(InternalShaderHelpers.ID._BloomTint, tint.value);
|
||||
material.SetFloat(InternalShaderHelpers.ID._BlurRadius, scatter.value);
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// 1. 参数打包(完全对齐 Unity SetupBloom 的计算逻辑)
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
|
||||
// 2. 初始化金字塔数组
|
||||
int iterations = Mathf.Clamp(diffusion.value, 1, k_MaxPyramidSize);
|
||||
|
||||
// 确保 RT 数组大小足够
|
||||
if (_bloomPyramidUp == null || _bloomPyramidUp.Length != k_MaxPyramidSize)
|
||||
// scatter: 用户 [0,1] → shader [0.05, 0.95](与原生 Lerp 映射一致)
|
||||
float scatterMapped = Mathf.Lerp(0.05f, 0.95f, scatter.value);
|
||||
|
||||
// threshold: Gamma → Linear(原生使用 GammaToLinearSpace 转换)
|
||||
float thresholdLinear = Mathf.GammaToLinearSpace(threshold.value);
|
||||
float thresholdKnee = thresholdLinear * 0.5f; // 硬编码 soft knee,与原生一致
|
||||
|
||||
material.SetVector(InternalShaderHelpers.ID._BloomScatterParams,
|
||||
new Vector4(scatterMapped, clamp.value, thresholdLinear, thresholdKnee));
|
||||
|
||||
material.SetFloat(InternalShaderHelpers.ID._AnimeBloom_KernelScale, kernelScale.value);
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// 2. Tint 归一化(原生做法:亮度归一为 1,只携带色相/饱和度)
|
||||
// 这样 tint 调整颜色时不会改变 bloom 总亮度。
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
Color tintLinear = tint.value.linear;
|
||||
float luma = 0.2126f * tintLinear.r + 0.7152f * tintLinear.g + 0.0722f * tintLinear.b;
|
||||
Color tintNormalized = luma > 0f ? tintLinear * (1f / luma) : Color.white;
|
||||
|
||||
// 将 intensity 和归一化 tint 一并打包进 _BloomParams(原生 uber 方式)
|
||||
material.SetVector(InternalShaderHelpers.ID._BloomParams,
|
||||
new Vector4(intensity.value, tintNormalized.r, tintNormalized.g, tintNormalized.b));
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// 3. RT 数组初始化
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
int mipCount = Mathf.Clamp(diffusion.value, 1, k_MaxMips);
|
||||
|
||||
if (_bloomMipDown == null || _bloomMipDown.Length != k_MaxMips)
|
||||
{
|
||||
_bloomPyramidUp = new RTHandle[k_MaxPyramidSize];
|
||||
_bloomPyramidDown = new RTHandle[k_MaxPyramidSize];
|
||||
_bloomMipDown = new RTHandle[k_MaxMips];
|
||||
_bloomMipUp = new RTHandle[k_MaxMips];
|
||||
}
|
||||
|
||||
// 3. Prefilter Pass (提取高亮)
|
||||
// 先降一半分辨率,节省性能且增加模糊感
|
||||
desc.width = Mathf.Max(1, desc.width >> 1);
|
||||
desc.height = Mathf.Max(1, desc.height >> 1);
|
||||
|
||||
RenderingUtils.ReAllocateIfNeeded(ref _bloomPyramidDown[0], desc, FilterMode.Bilinear, TextureWrapMode.Clamp, name: "_BloomMipDown0");
|
||||
RenderingUtils.ReAllocateIfNeeded(ref _bloomPyramidUp[0], desc, FilterMode.Bilinear, TextureWrapMode.Clamp, name: "_BloomMipUp0");
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// 4. Prefilter(提取高亮像素 + 初始激进降采样)
|
||||
// 根据用户选择,起始分辨率可能是 1/2, 1/4 或 1/8。
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
int shift = initialDownscaleShift.value;
|
||||
desc.width = Mathf.Max(1, desc.width >> shift);
|
||||
desc.height = Mathf.Max(1, desc.height >> shift);
|
||||
|
||||
// Source -> Down[0] (Prefilter)
|
||||
Blitter.BlitCameraTexture(cmd, source, _bloomPyramidDown[0], material, 0);
|
||||
RenderingUtils.ReAllocateIfNeeded(ref _bloomMipDown[0], desc, FilterMode.Bilinear,
|
||||
TextureWrapMode.Clamp, name: "_BloomDown0");
|
||||
|
||||
// 4. Downsample Loop (降采样金字塔)
|
||||
int lastDown = 0;
|
||||
for (int i = 1; i < iterations; i++)
|
||||
// Source → Down[0] via Pass 0 (Prefilter)
|
||||
Blitter.BlitCameraTexture(cmd, source, _bloomMipDown[0], material, 0);
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// 5. Downsample Loop:Dual Kawase 降采样金字塔(Pass 1)
|
||||
// 每次分辨率减半,产生多层不同尺度的模糊纹理
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
for (int i = 1; i < mipCount; i++)
|
||||
{
|
||||
// 每次分辨率减半
|
||||
desc.width = Mathf.Max(1, desc.width >> 1);
|
||||
desc.width = Mathf.Max(1, desc.width >> 1);
|
||||
desc.height = Mathf.Max(1, desc.height >> 1);
|
||||
|
||||
RenderingUtils.ReAllocateIfNeeded(ref _bloomPyramidDown[i], desc, FilterMode.Bilinear, TextureWrapMode.Clamp, name: "_BloomMipDown" + i);
|
||||
RenderingUtils.ReAllocateIfNeeded(ref _bloomPyramidUp[i], desc, FilterMode.Bilinear, TextureWrapMode.Clamp, name: "_BloomMipUp" + i);
|
||||
RenderingUtils.ReAllocateIfNeeded(ref _bloomMipDown[i], desc, FilterMode.Bilinear,
|
||||
TextureWrapMode.Clamp, name: "_BloomDown" + i);
|
||||
|
||||
// Down[i-1] -> Down[i]
|
||||
Blitter.BlitCameraTexture(cmd, _bloomPyramidDown[i - 1], _bloomPyramidDown[i], material, 1);
|
||||
lastDown = i;
|
||||
// Down[i-1] → Down[i] via Pass 1 (Dual Kawase Downsample)
|
||||
Blitter.BlitCameraTexture(cmd, _bloomMipDown[i - 1], _bloomMipDown[i], material, 1);
|
||||
}
|
||||
|
||||
// 5. Upsample Loop (升采样并混合)
|
||||
// 从最小的一张开始,往上叠加
|
||||
// 先把最小的 Down 直接拷给 Up
|
||||
Blitter.BlitCameraTexture(cmd, _bloomPyramidDown[lastDown], _bloomPyramidUp[lastDown]);
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// 6. Upsample Loop:Dual Kawase 升采样 + lerp(high, low, scatter)
|
||||
//
|
||||
// 与 Unity 原生完全一致的数据流向:
|
||||
// _BlitTexture = highMip = Down[i] (当前层高分辨率细节)
|
||||
// _SourceTexLowMip = lowMip = Up[i+1] 或 Down[last](低分辨率扩散光晕)
|
||||
// output = Up[i]
|
||||
//
|
||||
// 从最底层开始,逐层往上合并,最终 Up[0] 就是完整的 bloom 纹理
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
|
||||
for (int i = lastDown - 1; i >= 0; i--)
|
||||
// 分配所有 Up RT(从最大 mip 往上,分辨率逐步翻倍)
|
||||
// 由于 desc 已经在 downsample 中不断减半,我们要重新从 Down[i] 读尺寸
|
||||
for (int i = mipCount - 1; i >= 0; i--)
|
||||
{
|
||||
// 设置上一级 Up 为输入
|
||||
// Upsample Pass 会混合:Up[i+1] (Blur) + Down[i] (High Res Detail)
|
||||
// 这里我们稍微简化逻辑:直接把 Up[i+1] 升采样并叠加到 Up[i] 上
|
||||
// 第二步:把 Up[i+1] (LowRes) 和 Down[i] (HighRes) 一起传给 Shader
|
||||
// 在 Shader 内部执行 Lerp 能量守恒融合,彻底替代会导致死白的 Additive 叠加模式
|
||||
material.SetTexture("_BloomMipDown", _bloomPyramidDown[i]);
|
||||
Blitter.BlitCameraTexture(cmd, _bloomPyramidUp[i + 1], _bloomPyramidUp[i], material, 2);
|
||||
RenderingUtils.ReAllocateIfNeeded(ref _bloomMipUp[i],
|
||||
_bloomMipDown[i].rt.descriptor, FilterMode.Bilinear,
|
||||
TextureWrapMode.Clamp, name: "_BloomUp" + i);
|
||||
}
|
||||
|
||||
// 6. Composite (合成)
|
||||
// 此时 _bloomPyramidUp[0] 包含了最终的泛光纹理
|
||||
material.SetTexture(InternalShaderHelpers.ID._BloomTex, _bloomPyramidUp[0]);
|
||||
|
||||
// Source + BloomTex -> Destination
|
||||
// 最底层:lowMip = Down[last],highMip 也是 Down[last](无 low 可用,直接 lerp 自身,结果仍 = Down[last])
|
||||
// 简化做法:直接将 Down[last] 拷到 Up[last] 作为起点
|
||||
Blitter.BlitCameraTexture(cmd, _bloomMipDown[mipCount - 1], _bloomMipUp[mipCount - 1]);
|
||||
|
||||
// 从第二底层开始向上 upsample
|
||||
for (int i = mipCount - 2; i >= 0; i--)
|
||||
{
|
||||
// highMip:当前层降采样纹理 Down[i],作为 Blit 的 source(_BlitTexture)
|
||||
// lowMip: 上一级升采样结果 Up[i+1],通过 SetTexture 传入
|
||||
cmd.SetGlobalTexture(InternalShaderHelpers.ID._SourceTexLowMip, _bloomMipUp[i + 1]);
|
||||
Blitter.BlitCameraTexture(cmd, _bloomMipDown[i], _bloomMipUp[i], material, 2);
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// 7. Composite:将 bloom 叠加回原始画面(Pass 3)
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
material.SetTexture(InternalShaderHelpers.ID._BloomTex, _bloomMipUp[0]);
|
||||
Blitter.BlitCameraTexture(cmd, source, destination, material, 3);
|
||||
}
|
||||
|
||||
// 清理 RT
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_bloomPyramidDown != null)
|
||||
if (_bloomMipDown == null) return;
|
||||
for (int i = 0; i < _bloomMipDown.Length; i++)
|
||||
{
|
||||
for (int i = 0; i < _bloomPyramidDown.Length; i++)
|
||||
{
|
||||
if (_bloomPyramidDown[i] != null) _bloomPyramidDown[i].Release();
|
||||
if (_bloomPyramidUp[i] != null) _bloomPyramidUp[i].Release();
|
||||
}
|
||||
_bloomMipDown[i]?.Release();
|
||||
_bloomMipUp[i]?.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public override bool IsActive() => intensity.value > 0f;
|
||||
}
|
||||
}
|
||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user