diff --git a/Sources/Everglow.Function/VFX/Pipelines/ScreenReflectionPipeline.cs b/Sources/Everglow.Function/VFX/Pipelines/ScreenReflectionPipeline.cs index d79722ac1..b6a0e0901 100644 --- a/Sources/Everglow.Function/VFX/Pipelines/ScreenReflectionPipeline.cs +++ b/Sources/Everglow.Function/VFX/Pipelines/ScreenReflectionPipeline.cs @@ -11,6 +11,10 @@ public class ScreenReflectionPipeline : Pipeline { private RenderTarget2D screenReflectionScreen; // 反射区域 + protected virtual bool IncludeLocalPlayer => true; + + protected virtual Matrix CaptureTransform => Matrix.Invert(Main.GameViewMatrix.TransformationMatrix); + public override void Load() { Ins.MainThread.AddTask(() => @@ -43,16 +47,17 @@ public override void BeginRender() RasterizerState.CullNone); spriteBatch.Draw(Main.screenTarget, Vector2.Zero, Color.White); spriteBatch.End(); - List players = new List() { Main.LocalPlayer }; - - // 外加玩家 - Main.PlayerRenderer.DrawPlayers(Main.Camera, players); + if (IncludeLocalPlayer) + { + List players = new List() { Main.LocalPlayer }; + Main.PlayerRenderer.DrawPlayers(Main.Camera, players); + } graphicsDevice.SetRenderTarget(screenReflectionScreen); // 以另一种方法绘制保存下来的屏幕 spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.Opaque, SamplerState.PointClamp, DepthStencilState.Default, - RasterizerState.CullNone, null, Matrix.Invert(Main.GameViewMatrix.TransformationMatrix)); + RasterizerState.CullNone, null, CaptureTransform); spriteBatch.Draw(Main.screenTargetSwap, Vector2.Zero, Color.White); spriteBatch.End(); @@ -67,6 +72,14 @@ public override void BeginRender() spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.Additive, SamplerState.PointClamp, DepthStencilState.None, RasterizerState.CullNone); + ApplyReflectionEffect(); + Main.graphics.GraphicsDevice.Textures[0] = screenReflectionScreen; + Main.graphics.GraphicsDevice.SamplerStates[0] = SamplerState.PointWrap; + Main.graphics.GraphicsDevice.SamplerStates[1] = SamplerState.PointClamp; + } + + protected virtual void ApplyReflectionEffect() + { Effect ef = Commons.ModAsset.ScreenReflection_NormalColor.Value; var viewport = Main.graphics.GraphicsDevice.Viewport; @@ -85,9 +98,6 @@ public override void BeginRender() ef.Parameters["uViewProj"].SetValue(Main.GameViewMatrix.TransformationMatrix * projection); ef.CurrentTechnique.Passes["Test"].Apply(); - Main.graphics.GraphicsDevice.Textures[0] = screenReflectionScreen; - Main.graphics.GraphicsDevice.SamplerStates[0] = SamplerState.PointWrap; - Main.graphics.GraphicsDevice.SamplerStates[1] = SamplerState.PointClamp; } public override void Render(IEnumerable visuals) diff --git a/Sources/Modules/Yggdrasil/YggdrasilTown/NPCs/KingJellyBall/KingJellyBall_Reflection.cs b/Sources/Modules/Yggdrasil/YggdrasilTown/NPCs/KingJellyBall/KingJellyBall_Reflection.cs index 20426b54f..5371577aa 100644 --- a/Sources/Modules/Yggdrasil/YggdrasilTown/NPCs/KingJellyBall/KingJellyBall_Reflection.cs +++ b/Sources/Modules/Yggdrasil/YggdrasilTown/NPCs/KingJellyBall/KingJellyBall_Reflection.cs @@ -2,10 +2,10 @@ namespace Everglow.Yggdrasil.YggdrasilTown.NPCs.KingJellyBall; -[Pipeline(typeof(ScreenReflectionPipeline))] +[Pipeline(typeof(KingJellyBall_ReflectionPipeline))] public class KingJellyBall_Reflection : Visual { - public override CodeLayer DrawLayer => CodeLayer.PostDrawProjectiles; + public override CodeLayer DrawLayer => CodeLayer.PreDrawFilter; public NPC MyKingJellyBallOwner; @@ -51,20 +51,24 @@ public override void Draw() Active = false; return; } - - var textureOld = Main.graphics.GraphicsDevice.Textures[1]; - Main.graphics.GraphicsDevice.Textures[1] = ModAsset.KingJellyBall_Reflect.Value; float timeValue = (float)Main.time * 0.03f; List jellyBallBodyInner = new List(); // adjust center base on the polar funtion graph. Vector2 offset = new Vector2(0, -60) + new Vector2(0, -120 * (Scale - 0.3f)); Vector2 offsetedCenter = Position + offset; + Effect reflection = ModAsset.KingJellyBall_SurfaceReflection.Value; + float bodyAsymmetry = 110 + 10 * MathF.Sin(timeValue); + reflection.Parameters["uSurfaceCenter"].SetValue(offsetedCenter + new Vector2(0, 1.3f * bodyAsymmetry * Scale) - Main.screenPosition); + reflection.Parameters["uSurfaceScale"].SetValue(Scale); + reflection.Parameters["uTime"].SetValue(Main.GlobalTimeWrappedHourly); + reflection.Parameters["uRippleStrength"].SetValue(0.65f); + reflection.CurrentTechnique.Passes["Test"].Apply(); int step = 150; for (int theta = 0; theta <= step; theta++) { float a = 200; - float b = 110 + 10 * MathF.Sin(timeValue); + float b = bodyAsymmetry; float angle = theta / (float)step * MathHelper.TwoPi; float r = a - b * MathF.Sin(angle); @@ -79,22 +83,9 @@ public override void Draw() Vector2 toDistance = new Vector2(-r, 0).RotatedBy(angle); toDistance.Y *= 1.3f; Vector2 width = Vector2.Normalize(toDistance) * 2f; - jellyBallBodyInner.Add(new MirrorFaceVertex(new Vector3(offsetedCenter, -150), new Color(0.4f, 0.6f, 1f, 0.5f), new Vector3(0.5f, 0.5f, 0))); - jellyBallBodyInner.Add(new MirrorFaceVertex(new Vector3(offsetedCenter + toDistance - width, -10), new Color(0.7f, 0.7f, 1f, 1f), new Vector3(new Vector2(0.5f) + new Vector2(-0.27f, 0).RotatedBy(angle), 0))); - } - if (jellyBallBodyInner.Count >= 2) - { - Main.graphics.GraphicsDevice.DrawUserPrimitives(PrimitiveType.TriangleStrip, jellyBallBodyInner.ToArray(), 0, jellyBallBodyInner.Count - 2); - } - else - { - jellyBallBodyInner.Add(new MirrorFaceVertex(new Vector3(0), new Color(0.5f, 0.5f, 0.5f), new Vector3(0.5f, 0.5f, 0))); - jellyBallBodyInner.Add(new MirrorFaceVertex(new Vector3(0), new Color(0.5f, 0.5f, 0.5f), new Vector3(0.5f, 0.5f, 0))); - jellyBallBodyInner.Add(new MirrorFaceVertex(new Vector3(0), new Color(0.5f, 0.5f, 0.5f), new Vector3(0.5f, 0.5f, 0))); - jellyBallBodyInner.Add(new MirrorFaceVertex(new Vector3(0), new Color(0.5f, 0.5f, 0.5f), new Vector3(0.5f, 0.5f, 0))); - - Main.graphics.GraphicsDevice.DrawUserPrimitives(PrimitiveType.TriangleStrip, jellyBallBodyInner.ToArray(), 0, jellyBallBodyInner.Count - 2); + jellyBallBodyInner.Add(new MirrorFaceVertex(new Vector3(offsetedCenter, -1), new Color(0.4f, 0.6f, 1f, 0.5f), new Vector3(0.5f, 0.5f, 0))); + jellyBallBodyInner.Add(new MirrorFaceVertex(new Vector3(offsetedCenter + toDistance - width, -1), new Color(0.7f, 0.7f, 1f, 1f), new Vector3(new Vector2(0.5f) + new Vector2(-0.27f, 0).RotatedBy(angle), 0))); } - Main.graphics.GraphicsDevice.Textures[1] = textureOld; + Main.graphics.GraphicsDevice.DrawUserPrimitives(PrimitiveType.TriangleStrip, jellyBallBodyInner.ToArray(), 0, jellyBallBodyInner.Count - 2); } } diff --git a/Sources/Modules/Yggdrasil/YggdrasilTown/NPCs/KingJellyBall/KingJellyBall_ReflectionPipeline.cs b/Sources/Modules/Yggdrasil/YggdrasilTown/NPCs/KingJellyBall/KingJellyBall_ReflectionPipeline.cs new file mode 100644 index 000000000..edea6e713 --- /dev/null +++ b/Sources/Modules/Yggdrasil/YggdrasilTown/NPCs/KingJellyBall/KingJellyBall_ReflectionPipeline.cs @@ -0,0 +1,29 @@ +namespace Everglow.Yggdrasil.YggdrasilTown.NPCs.KingJellyBall; + +public class KingJellyBall_ReflectionPipeline : ScreenReflectionPipeline +{ + // PreDrawFilter captures the completed scene, including all players. + protected override bool IncludeLocalPlayer => false; + + protected override Matrix CaptureTransform => Matrix.Identity; + + public override void BeginRender() + { + base.BeginRender(); + Main.graphics.GraphicsDevice.SamplerStates[0] = SamplerState.LinearClamp; + } + + protected override void ApplyReflectionEffect() + { + Effect reflection = ModAsset.KingJellyBall_SurfaceReflection.Value; + var viewport = Main.graphics.GraphicsDevice.Viewport; + var projection = Matrix.CreateOrthographicOffCenter(0, viewport.Width, viewport.Height, 0, 0, 500); + reflection.Parameters["uModel"].SetValue(Matrix.CreateTranslation(new Vector3(-Main.screenPosition, 0))); + reflection.Parameters["uViewProj"].SetValue(Main.GameViewMatrix.TransformationMatrix * projection); + reflection.Parameters["uScreenTransform"].SetValue(Main.GameViewMatrix.TransformationMatrix); + reflection.Parameters["uViewportSize"].SetValue(new Vector2(viewport.Width, viewport.Height)); + reflection.Parameters["uFresnelF0"].SetValue(new Vector3(0.17f)); + reflection.Parameters["uKs"].SetValue(Vector3.One); + reflection.CurrentTechnique.Passes["Test"].Apply(); + } +} diff --git a/Sources/Modules/Yggdrasil/YggdrasilTown/VFXs/KingJellyBall_SurfaceReflection.fx b/Sources/Modules/Yggdrasil/YggdrasilTown/VFXs/KingJellyBall_SurfaceReflection.fx new file mode 100644 index 000000000..333e6f57f --- /dev/null +++ b/Sources/Modules/Yggdrasil/YggdrasilTown/VFXs/KingJellyBall_SurfaceReflection.fx @@ -0,0 +1,89 @@ +sampler uScreenBuffer : register(s0); + +float3 uFresnelF0; +float3 uKs; +float2 uViewportSize; +float4x4 uModel; +float4x4 uViewProj; +float4x4 uScreenTransform; +float2 uSurfaceCenter; +float uSurfaceScale; +float uTime; +float uRippleStrength; + +struct VSInput +{ + float3 Pos : POSITION0; + float4 Color : COLOR0; + float3 Texcoord : TEXCOORD0; +}; + +struct PSInput +{ + float4 Pos : SV_POSITION; + float4 Color : COLOR0; + float2 SurfacePosition : TEXCOORD0; +}; + +PSInput VertexShaderFunction(VSInput input) +{ + PSInput output; + float4 position = mul(float4(input.Pos, 1), uModel); + output.Pos = mul(position, uViewProj); + output.SurfacePosition = position.xy; + output.Color = input.Color; + return output; +} + +float4 PixelShaderFunction(PSInput input) : COLOR0 +{ + float scale = max(uSurfaceScale, 0.01); + float2 localPosition = input.SurfacePosition - uSurfaceCenter; + float2 radii = float2(260, 280) * scale; + float2 surface = localPosition / radii; + float radiusSquared = dot(surface, surface); + if (radiusSquared >= 1) + return 0; + + // A convex ellipsoidal cap facing the camera. The source plane is z = 0; + // the cap is closest at its center. Depth and normal describe the same surface. + float cap = sqrt(max(1 - radiusSquared, 0.0025)); + float depth = scale * (12 + 70 * (1 - cap)); + float2 gradient = 70 * scale * surface / (radii * cap); + + // Smooth traveling waves perturb both height and its analytic derivatives. + // Coordinates scale with the body, so shrinking cannot amplify the distortion. + float2 wavePosition = localPosition / scale; + float2 frequency0 = float2(0.035, 0.022); + float2 frequency1 = float2(-0.018, 0.041); + float phase0 = dot(wavePosition, frequency0) - uTime * 1.8; + float phase1 = dot(wavePosition, frequency1) + uTime * 1.3; + depth += scale * uRippleStrength * (0.65 * sin(phase0) + 0.35 * sin(phase1)); + gradient += uRippleStrength * (0.65 * cos(phase0) * frequency0 + 0.35 * cos(phase1) * frequency1); + + // All positions and derivatives use screen axes: positive Y points down. + float3 normal = normalize(float3(gradient, 1)); + float3 reflected = reflect(float3(0, 0, -1), normal); + float visibility = smoothstep(0.12, 0.3, reflected.z); + float2 sourcePosition = input.SurfacePosition + depth * reflected.xy / max(reflected.z, 0.12); + + // Keep the captured image in screen space; transform the intersection once. + float2 screenPosition = mul(float4(sourcePosition, 0, 1), uScreenTransform).xy; + float2 edgeDistance = min(screenPosition, uViewportSize - screenPosition); + visibility *= smoothstep(0, 8, min(edgeDistance.x, edgeDistance.y)); + float2 uv = clamp(screenPosition / uViewportSize, 0.5 / uViewportSize, 1 - 0.5 / uViewportSize); + + float3 fresnel = uFresnelF0 + (1 - uFresnelF0) * pow(saturate(1 - normal.z), 5); + float3 scene = pow(max(tex2D(uScreenBuffer, uv).rgb, 0), 2.2); + float3 color = pow(max(scene * uKs * fresnel, 0), 1 / 2.2); + return float4(color, 1) * input.Color * visibility; +} + +technique Technique1 +{ + pass Test + { + VertexShader = compile vs_3_0 VertexShaderFunction(); + PixelShader = compile ps_3_0 PixelShaderFunction(); + } +}