|
| 1 | +#region Copyright & License Information |
| 2 | +/* |
| 3 | + * Copyright 2007-2020 The d2 mod Developers (see AUTHORS) |
| 4 | + * This file is part of OpenRA, which is free software. It is made |
| 5 | + * available to you under the terms of the GNU General Public License |
| 6 | + * as published by the Free Software Foundation, either version 3 of |
| 7 | + * the License, or (at your option) any later version. For more |
| 8 | + * information, see COPYING. |
| 9 | + */ |
| 10 | +#endregion |
| 11 | + |
| 12 | +using System.Collections.Generic; |
| 13 | +using System.IO; |
| 14 | +using System.Linq; |
| 15 | +using OpenRA.Graphics; |
| 16 | +using OpenRA.Traits; |
| 17 | + |
| 18 | +namespace OpenRA.Mods.D2.Graphics |
| 19 | +{ |
| 20 | + [TraitLocation(SystemActors.World)] |
| 21 | + [Desc("Renders Dune 2 style screen-space pixel distortion effects.")] |
| 22 | + public class D2DistortionRendererInfo : TraitInfo |
| 23 | + { |
| 24 | + public readonly string FragmentShader = "d2|glsl/postprocess_textured_distortion.frag"; |
| 25 | + public readonly PostProcessPassType PassType = PostProcessPassType.AfterWorld; |
| 26 | + public readonly HashSet<D2DistortionStyle> Styles = new() { D2DistortionStyle.Sand, D2DistortionStyle.Sonic }; |
| 27 | + |
| 28 | + public override object Create(ActorInitializer init) { return new D2DistortionRenderer(this); } |
| 29 | + } |
| 30 | + |
| 31 | + public sealed class D2DistortionRenderer : IRenderPostProcessPass, INotifyActorDisposing |
| 32 | + { |
| 33 | + // Renderables enqueue these records during normal world rendering. The post-process pass |
| 34 | + // then replays OpenDUNE-style blurred sprites against the completed world texture. |
| 35 | + readonly struct Distortion |
| 36 | + { |
| 37 | + public readonly D2DistortionStyle Style; |
| 38 | + public readonly float3 Pos; |
| 39 | + public readonly Sprite Sprite; |
| 40 | + public readonly int BlurOffset; |
| 41 | + |
| 42 | + public Distortion(D2DistortionStyle style, float3 pos, Sprite sprite, int blurOffset) |
| 43 | + { |
| 44 | + Style = style; |
| 45 | + Pos = pos; |
| 46 | + Sprite = sprite; |
| 47 | + BlurOffset = blurOffset; |
| 48 | + } |
| 49 | + } |
| 50 | + |
| 51 | + sealed class D2DistortionShaderBindings : IShaderBindings |
| 52 | + { |
| 53 | + public D2DistortionShaderBindings(string fragmentShader) |
| 54 | + { |
| 55 | + // Reuse the engine's textured post-process vertex shader. It interprets vertex |
| 56 | + // positions as local screen-space pixel offsets from the Pos uniform and passes |
| 57 | + // through sprite-sheet UVs for the fragment shader's mask lookup. |
| 58 | + VertexShaderName = "postprocess_textured"; |
| 59 | + VertexShaderCode = ShaderBindings.GetShaderCode("postprocess_textured.vert"); |
| 60 | + FragmentShaderName = fragmentShader; |
| 61 | + |
| 62 | + using (var stream = Game.ModData.DefaultFileSystem.Open(fragmentShader)) |
| 63 | + using (var reader = new StreamReader(stream)) |
| 64 | + FragmentShaderCode = reader.ReadToEnd(); |
| 65 | + } |
| 66 | + |
| 67 | + public string VertexShaderName { get; } |
| 68 | + public string VertexShaderCode { get; } |
| 69 | + public string FragmentShaderName { get; } |
| 70 | + public string FragmentShaderCode { get; } |
| 71 | + public int Stride => Attributes.Sum(a => a.Components * 4); |
| 72 | + |
| 73 | + public ShaderVertexAttribute[] Attributes { get; } = |
| 74 | + { |
| 75 | + new("aVertexPosition", ShaderVertexAttributeType.Float, 2, 0), |
| 76 | + new("aVertexTexCoord", ShaderVertexAttributeType.Float, 2, 8) |
| 77 | + }; |
| 78 | + } |
| 79 | + |
| 80 | + readonly Renderer renderer; |
| 81 | + readonly IShader shader; |
| 82 | + readonly D2DistortionRendererInfo info; |
| 83 | + readonly IVertexBuffer<RenderPostProcessPassTexturedVertex> buffer; |
| 84 | + readonly List<Distortion> distortions = new(); |
| 85 | + readonly RenderPostProcessPassTexturedVertex[] vertices = new RenderPostProcessPassTexturedVertex[6]; |
| 86 | + static readonly int[] BlurOffsets = { 1, 3, 2, 5, 4, 3, 2, 1 }; |
| 87 | + static int blurIndex; |
| 88 | + static int blurTickCounter; |
| 89 | + const int BlurTickInterval = 6; |
| 90 | + |
| 91 | + public D2DistortionRenderer(D2DistortionRendererInfo info) |
| 92 | + { |
| 93 | + this.info = info; |
| 94 | + renderer = Game.Renderer; |
| 95 | + shader = renderer.CreateShader(new D2DistortionShaderBindings(info.FragmentShader)); |
| 96 | + buffer = renderer.CreateVertexBuffer<RenderPostProcessPassTexturedVertex>(6); |
| 97 | + } |
| 98 | + |
| 99 | + public bool Accepts(D2DistortionStyle style) |
| 100 | + { |
| 101 | + return info.Styles.Contains(style); |
| 102 | + } |
| 103 | + |
| 104 | + public void DrawSprite(float3 pos, Sprite sprite, D2DistortionStyle style) |
| 105 | + { |
| 106 | + // BlurOffset больше не вычисляется здесь — только в Draw(), |
| 107 | + // чтобы blurIndex продвигался один раз за рендер-пасс, а не за спрайт. |
| 108 | + distortions.Add(new Distortion(style, pos, sprite, 0)); |
| 109 | + } |
| 110 | + |
| 111 | + PostProcessPassType IRenderPostProcessPass.Type => info.PassType; |
| 112 | + bool IRenderPostProcessPass.Enabled => distortions.Count > 0; |
| 113 | + |
| 114 | + void UpdateVertices(in Distortion d) |
| 115 | + { |
| 116 | + var halfWidth = d.Sprite.Size.X / 2; |
| 117 | + var halfHeight = d.Sprite.Size.Y / 2; |
| 118 | + vertices[0] = new RenderPostProcessPassTexturedVertex(-halfWidth, -halfHeight, d.Sprite.Left, d.Sprite.Top); |
| 119 | + vertices[1] = new RenderPostProcessPassTexturedVertex(halfWidth, -halfHeight, d.Sprite.Right, d.Sprite.Top); |
| 120 | + vertices[2] = new RenderPostProcessPassTexturedVertex(halfWidth, halfHeight, d.Sprite.Right, d.Sprite.Bottom); |
| 121 | + vertices[3] = new RenderPostProcessPassTexturedVertex(halfWidth, halfHeight, d.Sprite.Right, d.Sprite.Bottom); |
| 122 | + vertices[4] = new RenderPostProcessPassTexturedVertex(-halfWidth, halfHeight, d.Sprite.Left, d.Sprite.Bottom); |
| 123 | + vertices[5] = new RenderPostProcessPassTexturedVertex(-halfWidth, -halfHeight, d.Sprite.Left, d.Sprite.Top); |
| 124 | + } |
| 125 | + |
| 126 | + void IRenderPostProcessPass.Draw(WorldRenderer wr) |
| 127 | + { |
| 128 | + if (++blurTickCounter >= BlurTickInterval) |
| 129 | + { |
| 130 | + blurTickCounter = 0; |
| 131 | + blurIndex = (blurIndex + 1) % BlurOffsets.Length; |
| 132 | + } |
| 133 | + |
| 134 | + var scroll = wr.Viewport.TopLeft; |
| 135 | + var size = renderer.WorldFrameBufferSize; |
| 136 | + var width = 2f / (renderer.WorldDownscaleFactor * size.Width); |
| 137 | + var height = 2f / (renderer.WorldDownscaleFactor * size.Height); |
| 138 | + |
| 139 | + shader.SetVec("Scroll", scroll.X, scroll.Y); |
| 140 | + shader.SetVec("p1", width, height); |
| 141 | + shader.SetVec("p2", -1, -1); |
| 142 | + shader.SetTexture("WorldTexture", Game.Renderer.WorldBufferSnapshot()); |
| 143 | + shader.PrepareRender(); |
| 144 | + |
| 145 | + for (var i = 0; i < distortions.Count; i++) |
| 146 | + { |
| 147 | + var d = distortions[i]; |
| 148 | + // Каждый сегмент получает следующий шаг таблицы — как три последовательных |
| 149 | + // вызова GUI_DrawSprite в оригинале в рамках одного игрового кадра. |
| 150 | + var blurOffset = BlurOffsets[(blurIndex + i) % BlurOffsets.Length]; |
| 151 | + |
| 152 | + UpdateVertices(d); |
| 153 | + buffer.SetData(vertices, 6); |
| 154 | + |
| 155 | + shader.SetVec("Pos", d.Pos.X, d.Pos.Y); |
| 156 | + shader.SetVec("Style", (float)d.Style); |
| 157 | + shader.SetVec("BlurOffset", blurOffset); |
| 158 | + shader.SetVec("MaskChannel", (float)d.Sprite.Channel); |
| 159 | + shader.SetTexture("MaskTexture", d.Sprite.Sheet.GetTexture()); |
| 160 | + renderer.DrawBatch(buffer, shader, 0, 6, PrimitiveType.TriangleList); |
| 161 | + } |
| 162 | + |
| 163 | + distortions.Clear(); |
| 164 | + } |
| 165 | + |
| 166 | + void INotifyActorDisposing.Disposing(Actor self) |
| 167 | + { |
| 168 | + buffer.Dispose(); |
| 169 | + } |
| 170 | + } |
| 171 | +} |
0 commit comments