Skip to content

Commit cc9c781

Browse files
Merge pull request #221 from EJTP/Standalone
Added FXAA
2 parents aa35574 + ecf2fd7 commit cc9c781

File tree

4 files changed

+252
-13
lines changed

4 files changed

+252
-13
lines changed
Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
Shader "Default/FXAA"
2+
3+
Properties
4+
{
5+
}
6+
7+
Pass "FXAA"
8+
{
9+
Tags { "RenderOrder" = "Opaque" }
10+
11+
Cull None
12+
ZTest Off
13+
ZWrite Off
14+
15+
GLSLPROGRAM
16+
17+
Vertex
18+
{
19+
layout (location = 0) in vec3 vertexPosition;
20+
layout (location = 1) in vec2 vertexTexCoord;
21+
22+
out vec2 TexCoords;
23+
24+
void main()
25+
{
26+
TexCoords = vertexTexCoord;
27+
gl_Position = vec4(vertexPosition, 1.0);
28+
}
29+
}
30+
31+
Fragment
32+
{
33+
#include "Fragment"
34+
35+
layout(location = 0) out vec4 OutputColor;
36+
37+
in vec2 TexCoords;
38+
39+
uniform sampler2D _MainTex;
40+
uniform vec2 _Resolution;
41+
uniform float _EdgeThresholdMin;
42+
uniform float _EdgeThresholdMax;
43+
uniform float _SubpixelQuality;
44+
45+
float GetLuminance(vec3 color) {
46+
return dot(color, vec3(0.299, 0.587, 0.114));
47+
}
48+
49+
vec3 FXAA311(vec2 texCoord) {
50+
float quality[12] = float[12](1.0, 1.0, 1.0, 1.0, 1.0, 1.5, 2.0, 2.0, 2.0, 2.0, 4.0, 8.0);
51+
int iterations = 12;
52+
53+
vec2 inverseScreenSize = 1.0 / _Resolution;
54+
ivec2 texelCoord = ivec2(texCoord * _Resolution);
55+
56+
vec3 colorCenter = texelFetch(_MainTex, texelCoord, 0).rgb;
57+
58+
float lumaCenter = GetLuminance(colorCenter);
59+
float lumaDown = GetLuminance(texelFetchOffset(_MainTex, texelCoord, 0, ivec2( 0, -1)).rgb);
60+
float lumaUp = GetLuminance(texelFetchOffset(_MainTex, texelCoord, 0, ivec2( 0, 1)).rgb);
61+
float lumaLeft = GetLuminance(texelFetchOffset(_MainTex, texelCoord, 0, ivec2(-1, 0)).rgb);
62+
float lumaRight = GetLuminance(texelFetchOffset(_MainTex, texelCoord, 0, ivec2( 1, 0)).rgb);
63+
64+
float lumaMin = min(lumaCenter, min(min(lumaDown, lumaUp), min(lumaLeft, lumaRight)));
65+
float lumaMax = max(lumaCenter, max(max(lumaDown, lumaUp), max(lumaLeft, lumaRight)));
66+
67+
float lumaRange = lumaMax - lumaMin;
68+
69+
// Early exit if no edge detected
70+
if (lumaRange < max(_EdgeThresholdMin, lumaMax * _EdgeThresholdMax)) {
71+
return colorCenter;
72+
}
73+
74+
// Sample corners
75+
float lumaDownLeft = GetLuminance(texelFetchOffset(_MainTex, texelCoord, 0, ivec2(-1, -1)).rgb);
76+
float lumaUpRight = GetLuminance(texelFetchOffset(_MainTex, texelCoord, 0, ivec2( 1, 1)).rgb);
77+
float lumaUpLeft = GetLuminance(texelFetchOffset(_MainTex, texelCoord, 0, ivec2(-1, 1)).rgb);
78+
float lumaDownRight = GetLuminance(texelFetchOffset(_MainTex, texelCoord, 0, ivec2( 1, -1)).rgb);
79+
80+
float lumaDownUp = lumaDown + lumaUp;
81+
float lumaLeftRight = lumaLeft + lumaRight;
82+
83+
float lumaLeftCorners = lumaDownLeft + lumaUpLeft;
84+
float lumaDownCorners = lumaDownLeft + lumaDownRight;
85+
float lumaRightCorners = lumaDownRight + lumaUpRight;
86+
float lumaUpCorners = lumaUpRight + lumaUpLeft;
87+
88+
// Detect horizontal/vertical edge
89+
float edgeHorizontal = abs(-2.0 * lumaLeft + lumaLeftCorners ) +
90+
abs(-2.0 * lumaCenter + lumaDownUp ) * 2.0 +
91+
abs(-2.0 * lumaRight + lumaRightCorners);
92+
float edgeVertical = abs(-2.0 * lumaUp + lumaUpCorners ) +
93+
abs(-2.0 * lumaCenter + lumaLeftRight ) * 2.0 +
94+
abs(-2.0 * lumaDown + lumaDownCorners );
95+
96+
bool isHorizontal = (edgeHorizontal >= edgeVertical);
97+
98+
float luma1 = isHorizontal ? lumaDown : lumaLeft;
99+
float luma2 = isHorizontal ? lumaUp : lumaRight;
100+
float gradient1 = luma1 - lumaCenter;
101+
float gradient2 = luma2 - lumaCenter;
102+
103+
bool is1Steepest = abs(gradient1) >= abs(gradient2);
104+
float gradientScaled = 0.25 * max(abs(gradient1), abs(gradient2));
105+
106+
float stepLength = isHorizontal ? inverseScreenSize.y : inverseScreenSize.x;
107+
108+
float lumaLocalAverage = 0.0;
109+
110+
if (is1Steepest) {
111+
stepLength = -stepLength;
112+
lumaLocalAverage = 0.5 * (luma1 + lumaCenter);
113+
} else {
114+
lumaLocalAverage = 0.5 * (luma2 + lumaCenter);
115+
}
116+
117+
vec2 currentUv = texCoord;
118+
if (isHorizontal) {
119+
currentUv.y += stepLength * 0.5;
120+
} else {
121+
currentUv.x += stepLength * 0.5;
122+
}
123+
124+
vec2 offset = isHorizontal ? vec2(inverseScreenSize.x, 0.0) : vec2(0.0, inverseScreenSize.y);
125+
126+
vec2 uv1 = currentUv - offset;
127+
vec2 uv2 = currentUv + offset;
128+
129+
float lumaEnd1 = GetLuminance(texture(_MainTex, uv1).rgb);
130+
float lumaEnd2 = GetLuminance(texture(_MainTex, uv2).rgb);
131+
lumaEnd1 -= lumaLocalAverage;
132+
lumaEnd2 -= lumaLocalAverage;
133+
134+
bool reached1 = abs(lumaEnd1) >= gradientScaled;
135+
bool reached2 = abs(lumaEnd2) >= gradientScaled;
136+
bool reachedBoth = reached1 && reached2;
137+
138+
if (!reached1) {
139+
uv1 -= offset;
140+
}
141+
if (!reached2) {
142+
uv2 += offset;
143+
}
144+
145+
if (!reachedBoth) {
146+
for (int i = 2; i < iterations; i++) {
147+
if (!reached1) {
148+
lumaEnd1 = GetLuminance(texture(_MainTex, uv1).rgb);
149+
lumaEnd1 = lumaEnd1 - lumaLocalAverage;
150+
}
151+
if (!reached2) {
152+
lumaEnd2 = GetLuminance(texture(_MainTex, uv2).rgb);
153+
lumaEnd2 = lumaEnd2 - lumaLocalAverage;
154+
}
155+
156+
reached1 = abs(lumaEnd1) >= gradientScaled;
157+
reached2 = abs(lumaEnd2) >= gradientScaled;
158+
reachedBoth = reached1 && reached2;
159+
160+
if (!reached1) {
161+
uv1 -= offset * quality[i];
162+
}
163+
if (!reached2) {
164+
uv2 += offset * quality[i];
165+
}
166+
167+
if (reachedBoth) break;
168+
}
169+
}
170+
171+
float distance1 = isHorizontal ? (texCoord.x - uv1.x) : (texCoord.y - uv1.y);
172+
float distance2 = isHorizontal ? (uv2.x - texCoord.x) : (uv2.y - texCoord.y);
173+
174+
bool isDirection1 = distance1 < distance2;
175+
float distanceFinal = min(distance1, distance2);
176+
177+
float edgeThickness = (distance1 + distance2);
178+
179+
float pixelOffset = -distanceFinal / edgeThickness + 0.5;
180+
181+
bool isLumaCenterSmaller = lumaCenter < lumaLocalAverage;
182+
183+
bool correctVariation = ((isDirection1 ? lumaEnd1 : lumaEnd2) < 0.0) != isLumaCenterSmaller;
184+
185+
float finalOffset = correctVariation ? pixelOffset : 0.0;
186+
187+
// Sub-pixel antialiasing
188+
float lumaAverage = (1.0 / 12.0) * (2.0 * (lumaDownUp + lumaLeftRight) + lumaLeftCorners + lumaRightCorners);
189+
float subPixelOffset1 = clamp(abs(lumaAverage - lumaCenter) / lumaRange, 0.0, 1.0);
190+
float subPixelOffset2 = (-2.0 * subPixelOffset1 + 3.0) * subPixelOffset1 * subPixelOffset1;
191+
float subPixelOffsetFinal = subPixelOffset2 * subPixelOffset2 * _SubpixelQuality;
192+
193+
finalOffset = max(finalOffset, subPixelOffsetFinal);
194+
195+
// Compute final UV coordinates
196+
vec2 finalUv = texCoord;
197+
if (isHorizontal) {
198+
finalUv.y += finalOffset * stepLength;
199+
} else {
200+
finalUv.x += finalOffset * stepLength;
201+
}
202+
203+
return texture(_MainTex, finalUv).rgb;
204+
}
205+
206+
void main()
207+
{
208+
vec3 color = FXAA311(TexCoords);
209+
OutputColor = vec4(color, 1.0);
210+
}
211+
}
212+
213+
ENDGLSL
214+
}

Prowl.Runtime/Rendering/DefaultRenderPipeline.cs

Lines changed: 36 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,29 @@
1919

2020
namespace Prowl.Runtime.Rendering
2121
{
22+
public sealed class FXAAEffect : ImageEffect
23+
{
24+
public float EdgeThresholdMax = 0.0625f; // 0.063 - 0.333 (lower = more AA, slower)
25+
public float EdgeThresholdMin = 0.0312f; // 0.0312 - 0.0833 (trims dark edges)
26+
public float SubpixelQuality = 0.75f; // 0.0 - 1.0 (subpixel AA amount)
27+
28+
private Material mat;
29+
30+
public override void OnRenderImage(RenderTexture source, RenderTexture destination)
31+
{
32+
mat ??= new Material(Shader.LoadDefault(DefaultShader.FXAA));
33+
34+
// Set shader parameters
35+
mat.SetFloat("_EdgeThresholdMax", EdgeThresholdMax);
36+
mat.SetFloat("_EdgeThresholdMin", EdgeThresholdMin);
37+
mat.SetFloat("_SubpixelQuality", SubpixelQuality);
38+
mat.SetVector("_Resolution", new Double2(source.Width, source.Height));
39+
40+
// Apply FXAA
41+
Graphics.Blit(source, destination, mat, 0);
42+
}
43+
}
44+
2245
public sealed class TonemapperEffect : ImageEffect
2346
{
2447
public override bool TransformsToLDR => true;
@@ -163,14 +186,14 @@ public sealed class ScreenSpaceReflectionEffect : ImageEffect
163186
public override void OnRenderImage(RenderTexture source, RenderTexture destination)
164187
{
165188
mat ??= new Material(Shader.LoadDefault(DefaultShader.SSR));
166-
189+
167190
// Set uniforms
168191
mat.SetInt("_RayStepCount", RayStepCount);
169192
mat.SetFloat("_ScreenEdgeFade", ScreenEdgeFade);
170-
193+
171194
// Set textures
172195
mat.SetTexture("_MainTex", source.MainTexture);
173-
196+
174197
// Apply effect
175198
Graphics.Blit(source, destination, mat, 0);
176199
}
@@ -285,7 +308,7 @@ private static (List<ImageEffect> all, List<ImageEffect> opaque, List<ImageEffec
285308
var all = new List<ImageEffect>();
286309
var opaqueEffects = new List<ImageEffect>();
287310
var finalEffects = new List<ImageEffect>();
288-
311+
289312
foreach (ImageEffect effect in camera.Effects)
290313
{
291314
all.Add(effect);
@@ -295,7 +318,7 @@ private static (List<ImageEffect> all, List<ImageEffect> opaque, List<ImageEffec
295318
else
296319
finalEffects.Add(effect);
297320
}
298-
321+
299322
return (all, opaqueEffects, finalEffects);
300323
}
301324

@@ -545,7 +568,7 @@ private static void SetupLightingAndShadows(CameraSnapshot css, IReadOnlyList<IR
545568
//PropertyState.SetGlobalInt("_LightCount", LightCount);
546569
PropertyState.SetGlobalVector("prowl_ShadowAtlasSize", new Double2(ShadowAtlas.GetSize(), ShadowAtlas.GetSize()));
547570
}
548-
571+
549572
private static void CreateLightBuffer(Double3 cameraPosition, LayerMask cullingMask, IReadOnlyList<IRenderableLight> lights, IReadOnlyList<IRenderable> renderables)
550573
{
551574
Graphics.Device.BindFramebuffer(ShadowAtlas.GetAtlas().frameBuffer);
@@ -691,8 +714,8 @@ private static void CreateLightBuffer(Double3 cameraPosition, LayerMask cullingM
691714
AtlasY = -1;
692715
AtlasWidth = 0;
693716
}
694-
695-
717+
718+
696719
if (light is DirectionalLight dirLight2)
697720
{
698721
// Return the light to its original position
@@ -732,8 +755,8 @@ private static void CreateLightBuffer(Double3 cameraPosition, LayerMask cullingM
732755
// Set the light counts
733756
PropertyState.SetGlobalInt("_SpotLightCount", spotLightIndex);
734757
PropertyState.SetGlobalInt("_PointLightCount", pointLightIndex);
735-
736-
758+
759+
737760
//unsafe
738761
//{
739762
// if (LightBuffer == null || gpuLights.Count > LightCount)
@@ -756,11 +779,11 @@ private static int CalculateResolution(double distance)
756779
double t = Maths.Clamp(distance / 48f, 0, 1);
757780
int tileSize = ShadowAtlas.GetTileSize();
758781
int resolution = Maths.RoundToInt(Maths.Lerp(ShadowAtlas.GetMaxShadowSize(), tileSize, t));
759-
782+
760783
// Round to nearest multiple of tile size
761784
return Maths.Max(tileSize, (resolution / tileSize) * tileSize);
762785
}
763-
786+
764787
private static Double3 GetSunDirection(IReadOnlyList<IRenderableLight> lights)
765788
{
766789
if (lights.Count > 0 && lights[0] is IRenderableLight light && light.GetLightType() == LightType.Directional)
@@ -778,7 +801,7 @@ private static void RenderGizmos(CameraSnapshot css)
778801
{
779802
Double4x4 vp = Maths.Mul(css.projection, css.view);
780803
(Mesh? wire, Mesh? solid) = Debug.GetGizmoDrawData(CAMERA_RELATIVE, css.cameraPosition);
781-
804+
782805
if (wire != null || solid != null)
783806
{
784807
// The vertices have already been transformed by the gizmo system to be camera relative (if needed) so we just need to draw them

Prowl.Runtime/Resources/DefaultAssets.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ public enum DefaultShader
1818
Tonemapper,
1919
TAA,
2020
SSR,
21+
FXAA,
2122
Bloom,
2223
BokehDoF,
2324
GBufferCombine,

Prowl.Runtime/Resources/Shader.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@ public static Shader LoadDefault(DefaultShader shader)
140140
DefaultShader.Tonemapper => "Tonemapper.shader",
141141
DefaultShader.TAA => "TAA.shader",
142142
DefaultShader.SSR => "SSR.shader",
143+
DefaultShader.FXAA => "FXAA.shader",
143144
DefaultShader.Bloom => "Bloom.shader",
144145
DefaultShader.BokehDoF => "BokehDoF.shader",
145146
DefaultShader.GBufferCombine => "GBuffercombine.shader",

0 commit comments

Comments
 (0)