像素化

This commit is contained in:
SoulliesOfficial
2025-06-30 09:25:29 -04:00
parent f31d77197b
commit e91f378989
19 changed files with 16279 additions and 17232 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -13533,16 +13533,16 @@ Canvas:
m_GameObject: {fileID: 4337614254416964835}
m_Enabled: 1
serializedVersion: 3
m_RenderMode: 1
m_RenderMode: 0
m_Camera: {fileID: 87189952}
m_PlaneDistance: 100
m_PixelPerfect: 0
m_PixelPerfect: 1
m_ReceivesEvents: 1
m_OverrideSorting: 0
m_OverridePixelPerfect: 0
m_SortingBucketNormalizedSize: 0
m_VertexColorAlwaysGammaSpace: 1
m_AdditionalShaderChannelsFlag: 25
m_AdditionalShaderChannelsFlag: 1
m_UpdateRectTransformForStandalone: 0
m_SortingLayerID: 0
m_SortingOrder: 0
@@ -15834,7 +15834,7 @@ MonoBehaviour:
m_TargetGraphic: {fileID: 4337614255116277774}
m_HandleRect: {fileID: 4337614255116277775}
m_Direction: 2
m_Value: 0
m_Value: 1
m_Size: 1
m_NumberOfSteps: 0
m_OnValueChanged:

View File

@@ -10,6 +10,19 @@ namespace Ichni
/// </summary>
public static class CustomCurvePresets
{
/// <summary>
/// 瞬间完成
/// </summary>
/// <returns></returns>
public static AnimationCurve Instant()
{
Keyframe[] keys = new Keyframe[2];
keys[0] = new Keyframe(0, 1, 0, 0);
keys[1] = new Keyframe(1, 1, 0, 0);
return new AnimationCurve(keys);
}
/// <summary>
/// 抛物线曲线,在中间达到最大值,两端为起始值
/// </summary>

View File

@@ -138,6 +138,7 @@ namespace Ichni.RhythmGame
{ "Vignette", new VignetteEffect(1, 1, 0.4f, Color.black, CustomCurvePresets.Parabolic(1, 0, 1)) },
{ "SetInteger", new SetIntegerEffect("New Variable", 0, false, 0, 1) },
{ "EnableControl", new EnableControlEffect(null, "New Variable", 0, false, "") },
{"Pixelate", new PixelateEffect(1, 320, 180, CustomCurvePresets.Instant())},
{ "LowPassFilter", new LowPassFilterEffect(1, 10, CustomCurvePresets.Parabolic(1, 0, 1)) },
{ "HighPassFilter", new HighPassFilterEffect(1, 22000, CustomCurvePresets.Parabolic(1, 0, 1)) },
{ "DTM_RippleEffect", new DTMRippleEffect(0.65f, Color.white, 0) }

View File

@@ -78,7 +78,10 @@ namespace Ichni.RhythmGame
public override EffectBase ConvertToGameType(GameElement attachedGameElement)
{
return new BloomEffect(duration, peak, intensityCurve);
return new BloomEffect(duration, peak, intensityCurve)
{
attachedGameElement = attachedGameElement
};
}
}
}

View File

@@ -0,0 +1,111 @@
using System.Collections;
using System.Collections.Generic;
using Ichni.Editor;
using Ichni.RhythmGame.Beatmap;
using UnityEngine;
namespace Ichni.RhythmGame
{
public class PixelateEffect : EffectBase
{
public float duration;
public float bottomX;
public float bottomY;
public AnimationCurve intensityCurve;
public PixelateEffect(float duration, float bottomX, float bottomY, AnimationCurve intensityCurve)
{
this.effectTime = duration;
this.duration = duration;
this.bottomX = bottomX;
this.bottomY = bottomY;
this.intensityCurve = intensityCurve;
}
public override void Recover()
{
EditorManager.instance.postProcessingManager.SetPixelateStrength(Screen.width, Screen.height);
EditorManager.instance.postProcessingManager.SetFeatureActive(false);
}
public override void Disrupt()
{
EditorManager.instance.postProcessingManager.SetPixelateStrength(Screen.width, Screen.height);
EditorManager.instance.postProcessingManager.SetFeatureActive(false);
}
public override void PreExecute()
{
EditorManager.instance.postProcessingManager.SetFeatureActive(true);
EditorManager.instance.postProcessingManager.SetPixelateStrength(Screen.width, Screen.height);
}
public override void Execute()
{
float x = Mathf.Lerp(Screen.width, bottomX, intensityCurve.Evaluate(effectProgressPercent));
float y = Mathf.Lerp(Screen.height, bottomY, intensityCurve.Evaluate(effectProgressPercent));
Debug.Log(x + ", " + y);
EditorManager.instance.postProcessingManager.SetPixelateStrength(x,y);
}
public override void Adjust()
{
EditorManager.instance.postProcessingManager.SetPixelateStrength(Screen.width, Screen.height);
EditorManager.instance.postProcessingManager.SetFeatureActive(false);
}
public override EffectBase_BM ConvertToBM()
{
return new PixelateEffect_BM(duration, bottomX, bottomY, intensityCurve);
}
public override void SetUpInspector()
{
IHaveInspection inspector = EditorManager.instance.uiManager.inspector;
var container = inspector.GenerateContainer("Pixelate Effect");
var effectSettings = container.GenerateSubcontainer(3);
var effectTimeField = inspector.GenerateInputField(this, effectSettings, "Effect Time", nameof(duration));
var bottomXField = inspector.GenerateInputField(this, effectSettings, "Bottom X", nameof(bottomX));
var bottomYField = inspector.GenerateInputField(this, effectSettings, "Bottom Y", nameof(bottomY));
var intensityCurveButton = inspector.GenerateButton(this, effectSettings, "Intensity Curve", () =>
{
var intensityCurveWindow =
inspector.GenerateCompositeParameterWindow(this, "Intensity Curve", nameof(intensityCurve)).SetAsCustomCurve();
});
var clearButton = inspector.GenerateButton(this, effectSettings, "Clear Pixelate", () =>
{
EditorManager.instance.postProcessingManager.SetFeatureActive(false);
});
}
}
namespace Beatmap
{
public class PixelateEffect_BM : EffectBase_BM
{
public float duration;
public float bottomX;
public float bottomY;
public AnimationCurve intensityCurve;
public PixelateEffect_BM(float duration, float bottomX, float bottomY, AnimationCurve intensityCurve)
{
this.effectTime = duration;
this.duration = duration;
this.bottomX = bottomX;
this.bottomY = bottomY;
this.intensityCurve = intensityCurve;
}
public override EffectBase ConvertToGameType(GameElement attachedGameElement)
{
return new PixelateEffect(duration, bottomX, bottomY, intensityCurve)
{
attachedGameElement = attachedGameElement
};
}
}
}
}

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 6d1fa92947bbef246a8a112b2a6b96c4
guid: b1ecccb5fd84627489e74a02a44da11a
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@@ -1,12 +1,90 @@
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Sirenix.OdinInspector;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
namespace Ichni.Editor
{
public class PostProcessingManager : MonoBehaviour
{
public Volume globalVolume;
public PixelateFeature pixelateFeature;
void Awake()
{
FindAndCacheFeatureWithReflection();
}
private void FindAndCacheFeatureWithReflection()
{
var pipelineAsset = GraphicsSettings.currentRenderPipeline as UniversalRenderPipelineAsset;
if (pipelineAsset == null)
{
Debug.LogError("当前渲染管线不是 UniversalRenderPipelineAsset。");
return;
}
// 2. 使用反射来获取内部的 m_RendererDataList 字段
FieldInfo rendererDataListField = typeof(UniversalRenderPipelineAsset).GetField("m_RendererDataList", BindingFlags.NonPublic | BindingFlags.Instance);
if (rendererDataListField == null)
{
Debug.LogError("在 UniversalRenderPipelineAsset 中无法通过反射找到 'm_RendererDataList' 字段。API可能已在你的URP版本中更改。");
return;
}
var rendererDataList = rendererDataListField.GetValue(pipelineAsset) as ScriptableRendererData[];
if (rendererDataList == null)
{
Debug.LogError("获取渲染器数据列表失败。");
return;
}
// 3. 遍历获取到的列表来查找我们的Feature
foreach (var rendererData in rendererDataList)
{
if (rendererData == null) continue;
var feature = rendererData.rendererFeatures.OfType<PixelateFeature>().FirstOrDefault();
if (feature != null)
{
pixelateFeature = feature;
Debug.Log("成功找到并缓存 pixelateFeature (通过反射)!");
break;
}
}
if (pixelateFeature == null)
{
Debug.LogError("在所有 RendererData 中都未找到 pixelateFeature。");
}
}
[Button]
public void SetFeatureActive(bool enable)
{
if (pixelateFeature != null)
{
pixelateFeature.SetActive(enable);
}
}
[Button]
public void SetPixelateStrength(float strengthX, float strengthY)
{
if (pixelateFeature != null)
{
pixelateFeature.settings.pixelateStrengthX = strengthX;
pixelateFeature.settings.pixelateStrengthY = strengthY;
pixelateFeature.pixelatePass.UpdateConfig(strengthX, strengthY);
}
else
{
Debug.LogError("Pixelate feature is not initialized.");
}
}
}
}

View File

@@ -14,8 +14,10 @@ MonoBehaviour:
m_EditorClassIdentifier:
m_Active: 0
settings:
renderPassEvent: 600
pixelateMaterial: {fileID: 2100000, guid: f0cf39229422d1b428bf04e0bc828dc6, type: 2}
pixelateShader: {fileID: 0}
pixelateStrengthX: 64
pixelateStrengthY: 64
passEvent: 500
--- !u!114 &-1878332245247344467
MonoBehaviour:
m_ObjectHideFlags: 0
@@ -51,6 +53,24 @@ MonoBehaviour:
- {fileID: 2800000, guid: 3302065f671a8450b82c9ddf07426f3a, type: 3}
- {fileID: 2800000, guid: 56a77a3e8d64f47b6afe9e3c95cb57d5, type: 3}
m_Shader: {fileID: 4800000, guid: 0849e84e3d62649e8882e9d6f056a017, type: 3}
--- !u!114 &-545220758568441603
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: f41bcf9b1f0eb6446907e2c52c9f2d39, type: 3}
m_Name: PixelateFeature
m_EditorClassIdentifier:
m_Active: 1
settings:
pixelateShader: {fileID: 4800000, guid: 272e7eef87baea8408e583d2670e66dd, type: 3}
pixelateStrengthX: 1621.1226
pixelateStrengthY: 911.8815
passEvent: 500
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
@@ -70,9 +90,9 @@ MonoBehaviour:
m_RendererFeatures:
- {fileID: -1878332245247344467}
- {fileID: 9211144479881546400}
- {fileID: -5115341559200278812}
- {fileID: 2224949836353836081}
m_RendererFeatureMap: adc0de57c6d2eee5a0ca230a1a8fd47fe4161343f4a702b93114ef7e169ce01e
- {fileID: -545220758568441603}
m_RendererFeatureMap: adc0de57c6d2eee5a0ca230a1a8fd47f3114ef7e169ce01efde85bd99bfc6ef8
m_UseNativeRenderPass: 0
postProcessData: {fileID: 11400000, guid: 41439944d30ece34e96484bdb6645b55, type: 2}
xrSystemData: {fileID: 11400000, guid: 60e1133243b97e347b653163a8c01b64, type: 2}

View File

@@ -850,6 +850,7 @@
}
]
},
"MotionAngles" : false,
"elementName" : "New Track Percent Point",
"tags" : [
@@ -1362,6 +1363,7 @@
}
]
},
"MotionAngles" : false,
"elementName" : "New Track Percent Point",
"tags" : [
@@ -1874,6 +1876,7 @@
}
]
},
"MotionAngles" : false,
"elementName" : "New Track Percent Point",
"tags" : [
@@ -69943,6 +69946,34 @@
},
"emissionIntensity" : 0,
"effectTime" : 0
},{
"__type" : "Ichni.RhythmGame.Beatmap.PixelateEffect_BM,Assembly-CSharp",
"duration" : 5,
"bottomX" : 320,
"bottomY" : 180,
"intensityCurve" : {
"keys" : [
{
"time" : 0,
"value" : 0,
"inTangent" : 0,
"outTangent" : 0
},{
"time" : 0.5,
"value" : 1,
"inTangent" : 0,
"outTangent" : 0
},{
"time" : 1,
"value" : 0,
"inTangent" : 0,
"outTangent" : 0
}
],
"preWrapMode" : 8,
"postWrapMode" : 8
},
"effectTime" : 5
}
],"Late":[

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,21 @@
Shader "Hidden/Custom/Pixelate"
{
SubShader
{
Tags { "RenderPipeline" = "UniversalPipeline" }
Pass
{
Name "Pixelate Pass"
ZTest Always
Cull Off
ZWrite Off
HLSLPROGRAM
#pragma vertex PixelatePassVert
#pragma fragment PixelatePassFrag
#include "PixelatePass.hlsl"
ENDHLSL
}
}
}

View File

@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 272e7eef87baea8408e583d2670e66dd
ShaderImporter:
externalObjects: {}
defaultTextures: []
nonModifiableTextures: []
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,47 +1,100 @@
using UnityEngine;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
public class PixelateFeature : ScriptableRendererFeature
{
[System.Serializable]
public class Settings
public class PixelatePass : ScriptableRenderPass
{
public RenderPassEvent renderPassEvent = RenderPassEvent.AfterRenderingPostProcessing;
[Tooltip("用于像素化的材质")]
public Material pixelateMaterial = null;
private static readonly string ProfilerTag = "PixelateEffect";
private static readonly int PixelateStrengthXID = Shader.PropertyToID("_PixelateStrengthX");
private static readonly int PixelateStrengthYID = Shader.PropertyToID("_PixelateStrengthY");
private static readonly int SourceTextureID = Shader.PropertyToID("_SourceTexture");
public Material pixelateMaterial;
public float pixelateX = 64f;
public float pixelateY = 64f;
private RTHandle sourceRT;
private RTHandle transientRT;
public override void OnCameraSetup(CommandBuffer cmd, ref RenderingData renderingData)
{
sourceRT = renderingData.cameraData.renderer.cameraColorTargetHandle;
RenderTextureDescriptor descriptor = renderingData.cameraData.cameraTargetDescriptor;
descriptor.depthBufferBits = 0;
RenderingUtils.ReAllocateIfNeeded(ref transientRT, descriptor, name: "_PixelateTransientRT");
}
public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
{
if (pixelateMaterial == null) return;
CommandBuffer cmd = CommandBufferPool.Get(ProfilerTag);
pixelateMaterial.SetFloat(PixelateStrengthXID, pixelateX);
pixelateMaterial.SetFloat(PixelateStrengthYID, pixelateY);
cmd.SetGlobalTexture(SourceTextureID, sourceRT);
cmd.SetRenderTarget(transientRT, RenderBufferLoadAction.DontCare, RenderBufferStoreAction.Store);
cmd.DrawProcedural(Matrix4x4.identity, pixelateMaterial, 0, MeshTopology.Triangles, 3);
cmd.SetGlobalTexture(SourceTextureID, transientRT);
cmd.SetRenderTarget(sourceRT, RenderBufferLoadAction.DontCare, RenderBufferStoreAction.Store);
cmd.DrawProcedural(Matrix4x4.identity, pixelateMaterial, 0, MeshTopology.Triangles, 3);
context.ExecuteCommandBuffer(cmd);
CommandBufferPool.Release(cmd);
}
public void UpdateConfig(float x, float y)
{
pixelateX = x;
pixelateY = y;
}
}
public Settings settings = new Settings();
private PixelatePass m_PixelatePass;
[System.Serializable]
public class PixelateSettings
{
[SerializeField] private Shader pixelateShader;
private Material pixelateMaterial;
public Material PixelateMaterial
{
get
{
if (pixelateMaterial == null && pixelateShader != null)
{
pixelateMaterial = new Material(pixelateShader)
{
hideFlags = HideFlags.HideAndDontSave
};
}
return pixelateMaterial;
}
}
[Min(2)] public float pixelateStrengthX = 64f;
[Min(2)] public float pixelateStrengthY = 64f;
public RenderPassEvent passEvent = RenderPassEvent.AfterRenderingTransparents;
}
public PixelateSettings settings = new();
public PixelatePass pixelatePass;
// 当Feature被创建或Inspector中的值被改变时调用
public override void Create()
{
// 检查材质是否存在
if (settings.pixelateMaterial != null)
pixelatePass = new PixelatePass
{
m_PixelatePass = new PixelatePass(settings.pixelateMaterial);
// 将Inspector中设置的事件赋值给Pass
m_PixelatePass.renderPassEvent = settings.renderPassEvent;
}
else
{
// 如果材质为空则不创建Pass避免后续报错
m_PixelatePass = null;
}
renderPassEvent = settings.passEvent,
pixelateMaterial = settings.PixelateMaterial,
pixelateX = settings.pixelateStrengthX,
pixelateY = settings.pixelateStrengthY
};
}
// 【核心修正】这个方法现在非常干净只负责将创建好的Pass入队
public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
{
if (m_PixelatePass == null)
{
// 如果Pass没有被成功创建因为没材质就直接返回
return;
}
// 将我们的Pass添加到渲染队列中URP会在正确的时间执行它
renderer.EnqueuePass(m_PixelatePass);
renderer.EnqueuePass(pixelatePass);
}
}
}

View File

@@ -1,45 +0,0 @@
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
public class PixelatePass : ScriptableRenderPass
{
private Material m_PixelateMaterial;
// 构造函数,接收材质
public PixelatePass(Material pixelateMaterial)
{
this.m_PixelateMaterial = pixelateMaterial;
}
// 这个方法在每一帧渲染该Pass之前被调用
public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
{
// 1. 安全检查
if (m_PixelateMaterial == null)
{
Debug.LogError("Pixelate Material not assigned to the pass.");
return;
}
// 如果渲染的不是游戏主相机例如Scene视图的相机则直接返回避免在编辑器里也显示效果
if (renderingData.cameraData.cameraType != CameraType.Game)
{
return;
}
// 2. 获取命令缓冲区
CommandBuffer cmd = CommandBufferPool.Get("PixelatePass");
// 3. 【核心修正】在Execute方法内部安全地获取当前摄像机的渲染目标
// URP 12+ 使用 renderingData.cameraData.renderer.cameraColorTargetHandle
RTHandle source = renderingData.cameraData.renderer.cameraColorTargetHandle;
// 4. 执行Blit操作
// 将源纹理(source)通过我们的材质处理后,再写回源纹理(source)
Blit(cmd, source, source, m_PixelateMaterial, 0);
// 5. 执行并释放命令缓冲区
context.ExecuteCommandBuffer(cmd);
CommandBufferPool.Release(cmd);
}
}

View File

@@ -0,0 +1,50 @@
#ifndef PIXELATE_PASS_INCLUDED
#define PIXELATE_PASS_INCLUDED
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
TEXTURE2D(_SourceTexture);
SAMPLER(sampler_SourceTexture);
float4 _SourceTexture_TexelSize;
float _PixelateStrengthX;
float _PixelateStrengthY;
struct Varyings
{
float4 positionCS_SS : SV_POSITION;
float2 screenUV : VAR_SCREEN_UV;
};
Varyings PixelatePassVert(uint vertexID : SV_VertexID)
{
Varyings output;
output.positionCS_SS = float4(
vertexID <= 1 ? -1.0 : 3.0,
vertexID == 1 ? 3.0 : -1.0,
0.0, 1.0
);
output.screenUV = float2(
vertexID <= 1 ? 0.0 : 2.0,
vertexID == 1 ? 2.0 : 0.0
);
if (_ProjectionParams.x < 0.0)
{
output.screenUV.y = 1.0 - output.screenUV.y;
}
return output;
}
half4 PixelatePassFrag(Varyings input) : SV_Target
{
float2 screenUV = input.screenUV;
float2 strength = float2(_PixelateStrengthX, _PixelateStrengthY);
float2 uvScaled = screenUV * strength;
float2 uvFloor = floor(uvScaled);
float2 uvCenter = uvFloor + 0.5;
float2 uvPixelated = uvCenter / strength;
return SAMPLE_TEXTURE2D(_SourceTexture, sampler_SourceTexture, uvPixelated);
}
#endif

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 8be62d4e6aca4494caf6095e90160cd9
ShaderIncludeImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant: