Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
214 changes: 214 additions & 0 deletions Prowl.Runtime/Assets/Defaults/FXAA.shader
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
Shader "Default/FXAA"

Properties
{
}

Pass "FXAA"
{
Tags { "RenderOrder" = "Opaque" }

Cull None
ZTest Off
ZWrite Off

GLSLPROGRAM

Vertex
{
layout (location = 0) in vec3 vertexPosition;
layout (location = 1) in vec2 vertexTexCoord;

out vec2 TexCoords;

void main()
{
TexCoords = vertexTexCoord;
gl_Position = vec4(vertexPosition, 1.0);
}
}

Fragment
{
#include "Fragment"

layout(location = 0) out vec4 OutputColor;

in vec2 TexCoords;

uniform sampler2D _MainTex;
uniform vec2 _Resolution;
uniform float _EdgeThresholdMin;
uniform float _EdgeThresholdMax;
uniform float _SubpixelQuality;

float GetLuminance(vec3 color) {
return dot(color, vec3(0.299, 0.587, 0.114));
}

vec3 FXAA311(vec2 texCoord) {
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);
int iterations = 12;

vec2 inverseScreenSize = 1.0 / _Resolution;
ivec2 texelCoord = ivec2(texCoord * _Resolution);

vec3 colorCenter = texelFetch(_MainTex, texelCoord, 0).rgb;

float lumaCenter = GetLuminance(colorCenter);
float lumaDown = GetLuminance(texelFetchOffset(_MainTex, texelCoord, 0, ivec2( 0, -1)).rgb);
float lumaUp = GetLuminance(texelFetchOffset(_MainTex, texelCoord, 0, ivec2( 0, 1)).rgb);
float lumaLeft = GetLuminance(texelFetchOffset(_MainTex, texelCoord, 0, ivec2(-1, 0)).rgb);
float lumaRight = GetLuminance(texelFetchOffset(_MainTex, texelCoord, 0, ivec2( 1, 0)).rgb);

float lumaMin = min(lumaCenter, min(min(lumaDown, lumaUp), min(lumaLeft, lumaRight)));
float lumaMax = max(lumaCenter, max(max(lumaDown, lumaUp), max(lumaLeft, lumaRight)));

float lumaRange = lumaMax - lumaMin;

// Early exit if no edge detected
if (lumaRange < max(_EdgeThresholdMin, lumaMax * _EdgeThresholdMax)) {
return colorCenter;
}

// Sample corners
float lumaDownLeft = GetLuminance(texelFetchOffset(_MainTex, texelCoord, 0, ivec2(-1, -1)).rgb);
float lumaUpRight = GetLuminance(texelFetchOffset(_MainTex, texelCoord, 0, ivec2( 1, 1)).rgb);
float lumaUpLeft = GetLuminance(texelFetchOffset(_MainTex, texelCoord, 0, ivec2(-1, 1)).rgb);
float lumaDownRight = GetLuminance(texelFetchOffset(_MainTex, texelCoord, 0, ivec2( 1, -1)).rgb);

float lumaDownUp = lumaDown + lumaUp;
float lumaLeftRight = lumaLeft + lumaRight;

float lumaLeftCorners = lumaDownLeft + lumaUpLeft;
float lumaDownCorners = lumaDownLeft + lumaDownRight;
float lumaRightCorners = lumaDownRight + lumaUpRight;
float lumaUpCorners = lumaUpRight + lumaUpLeft;

// Detect horizontal/vertical edge
float edgeHorizontal = abs(-2.0 * lumaLeft + lumaLeftCorners ) +
abs(-2.0 * lumaCenter + lumaDownUp ) * 2.0 +
abs(-2.0 * lumaRight + lumaRightCorners);
float edgeVertical = abs(-2.0 * lumaUp + lumaUpCorners ) +
abs(-2.0 * lumaCenter + lumaLeftRight ) * 2.0 +
abs(-2.0 * lumaDown + lumaDownCorners );

bool isHorizontal = (edgeHorizontal >= edgeVertical);

float luma1 = isHorizontal ? lumaDown : lumaLeft;
float luma2 = isHorizontal ? lumaUp : lumaRight;
float gradient1 = luma1 - lumaCenter;
float gradient2 = luma2 - lumaCenter;

bool is1Steepest = abs(gradient1) >= abs(gradient2);
float gradientScaled = 0.25 * max(abs(gradient1), abs(gradient2));

float stepLength = isHorizontal ? inverseScreenSize.y : inverseScreenSize.x;

float lumaLocalAverage = 0.0;

if (is1Steepest) {
stepLength = -stepLength;
lumaLocalAverage = 0.5 * (luma1 + lumaCenter);
} else {
lumaLocalAverage = 0.5 * (luma2 + lumaCenter);
}

vec2 currentUv = texCoord;
if (isHorizontal) {
currentUv.y += stepLength * 0.5;
} else {
currentUv.x += stepLength * 0.5;
}

vec2 offset = isHorizontal ? vec2(inverseScreenSize.x, 0.0) : vec2(0.0, inverseScreenSize.y);

vec2 uv1 = currentUv - offset;
vec2 uv2 = currentUv + offset;

float lumaEnd1 = GetLuminance(texture(_MainTex, uv1).rgb);
float lumaEnd2 = GetLuminance(texture(_MainTex, uv2).rgb);
lumaEnd1 -= lumaLocalAverage;
lumaEnd2 -= lumaLocalAverage;

bool reached1 = abs(lumaEnd1) >= gradientScaled;
bool reached2 = abs(lumaEnd2) >= gradientScaled;
bool reachedBoth = reached1 && reached2;

if (!reached1) {
uv1 -= offset;
}
if (!reached2) {
uv2 += offset;
}

if (!reachedBoth) {
for (int i = 2; i < iterations; i++) {
if (!reached1) {
lumaEnd1 = GetLuminance(texture(_MainTex, uv1).rgb);
lumaEnd1 = lumaEnd1 - lumaLocalAverage;
}
if (!reached2) {
lumaEnd2 = GetLuminance(texture(_MainTex, uv2).rgb);
lumaEnd2 = lumaEnd2 - lumaLocalAverage;
}

reached1 = abs(lumaEnd1) >= gradientScaled;
reached2 = abs(lumaEnd2) >= gradientScaled;
reachedBoth = reached1 && reached2;

if (!reached1) {
uv1 -= offset * quality[i];
}
if (!reached2) {
uv2 += offset * quality[i];
}

if (reachedBoth) break;
}
}

float distance1 = isHorizontal ? (texCoord.x - uv1.x) : (texCoord.y - uv1.y);
float distance2 = isHorizontal ? (uv2.x - texCoord.x) : (uv2.y - texCoord.y);

bool isDirection1 = distance1 < distance2;
float distanceFinal = min(distance1, distance2);

float edgeThickness = (distance1 + distance2);

float pixelOffset = -distanceFinal / edgeThickness + 0.5;

bool isLumaCenterSmaller = lumaCenter < lumaLocalAverage;

bool correctVariation = ((isDirection1 ? lumaEnd1 : lumaEnd2) < 0.0) != isLumaCenterSmaller;

float finalOffset = correctVariation ? pixelOffset : 0.0;

// Sub-pixel antialiasing
float lumaAverage = (1.0 / 12.0) * (2.0 * (lumaDownUp + lumaLeftRight) + lumaLeftCorners + lumaRightCorners);
float subPixelOffset1 = clamp(abs(lumaAverage - lumaCenter) / lumaRange, 0.0, 1.0);
float subPixelOffset2 = (-2.0 * subPixelOffset1 + 3.0) * subPixelOffset1 * subPixelOffset1;
float subPixelOffsetFinal = subPixelOffset2 * subPixelOffset2 * _SubpixelQuality;

finalOffset = max(finalOffset, subPixelOffsetFinal);

// Compute final UV coordinates
vec2 finalUv = texCoord;
if (isHorizontal) {
finalUv.y += finalOffset * stepLength;
} else {
finalUv.x += finalOffset * stepLength;
}

return texture(_MainTex, finalUv).rgb;
}

void main()
{
vec3 color = FXAA311(TexCoords);
OutputColor = vec4(color, 1.0);
}
}

ENDGLSL
}
49 changes: 36 additions & 13 deletions Prowl.Runtime/Rendering/DefaultRenderPipeline.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,29 @@

namespace Prowl.Runtime.Rendering
{
public sealed class FXAAEffect : ImageEffect
{
public float EdgeThresholdMax = 0.0625f; // 0.063 - 0.333 (lower = more AA, slower)
public float EdgeThresholdMin = 0.0312f; // 0.0312 - 0.0833 (trims dark edges)
public float SubpixelQuality = 0.75f; // 0.0 - 1.0 (subpixel AA amount)

private Material mat;

public override void OnRenderImage(RenderTexture source, RenderTexture destination)
{
mat ??= new Material(Shader.LoadDefault(DefaultShader.FXAA));

// Set shader parameters
mat.SetFloat("_EdgeThresholdMax", EdgeThresholdMax);
mat.SetFloat("_EdgeThresholdMin", EdgeThresholdMin);
mat.SetFloat("_SubpixelQuality", SubpixelQuality);
mat.SetVector("_Resolution", new Double2(source.Width, source.Height));

// Apply FXAA
Graphics.Blit(source, destination, mat, 0);
}
}

public sealed class TonemapperEffect : ImageEffect
{
public override bool TransformsToLDR => true;
Expand Down Expand Up @@ -163,14 +186,14 @@ public sealed class ScreenSpaceReflectionEffect : ImageEffect
public override void OnRenderImage(RenderTexture source, RenderTexture destination)
{
mat ??= new Material(Shader.LoadDefault(DefaultShader.SSR));

// Set uniforms
mat.SetInt("_RayStepCount", RayStepCount);
mat.SetFloat("_ScreenEdgeFade", ScreenEdgeFade);

// Set textures
mat.SetTexture("_MainTex", source.MainTexture);

// Apply effect
Graphics.Blit(source, destination, mat, 0);
}
Expand Down Expand Up @@ -285,7 +308,7 @@ private static (List<ImageEffect> all, List<ImageEffect> opaque, List<ImageEffec
var all = new List<ImageEffect>();
var opaqueEffects = new List<ImageEffect>();
var finalEffects = new List<ImageEffect>();

foreach (ImageEffect effect in camera.Effects)
{
all.Add(effect);
Expand All @@ -295,7 +318,7 @@ private static (List<ImageEffect> all, List<ImageEffect> opaque, List<ImageEffec
else
finalEffects.Add(effect);
}

return (all, opaqueEffects, finalEffects);
}

Expand Down Expand Up @@ -545,7 +568,7 @@ private static void SetupLightingAndShadows(CameraSnapshot css, IReadOnlyList<IR
//PropertyState.SetGlobalInt("_LightCount", LightCount);
PropertyState.SetGlobalVector("prowl_ShadowAtlasSize", new Double2(ShadowAtlas.GetSize(), ShadowAtlas.GetSize()));
}

private static void CreateLightBuffer(Double3 cameraPosition, LayerMask cullingMask, IReadOnlyList<IRenderableLight> lights, IReadOnlyList<IRenderable> renderables)
{
Graphics.Device.BindFramebuffer(ShadowAtlas.GetAtlas().frameBuffer);
Expand Down Expand Up @@ -691,8 +714,8 @@ private static void CreateLightBuffer(Double3 cameraPosition, LayerMask cullingM
AtlasY = -1;
AtlasWidth = 0;
}


if (light is DirectionalLight dirLight2)
{
// Return the light to its original position
Expand Down Expand Up @@ -732,8 +755,8 @@ private static void CreateLightBuffer(Double3 cameraPosition, LayerMask cullingM
// Set the light counts
PropertyState.SetGlobalInt("_SpotLightCount", spotLightIndex);
PropertyState.SetGlobalInt("_PointLightCount", pointLightIndex);


//unsafe
//{
// if (LightBuffer == null || gpuLights.Count > LightCount)
Expand All @@ -756,11 +779,11 @@ private static int CalculateResolution(double distance)
double t = Maths.Clamp(distance / 48f, 0, 1);
int tileSize = ShadowAtlas.GetTileSize();
int resolution = Maths.RoundToInt(Maths.Lerp(ShadowAtlas.GetMaxShadowSize(), tileSize, t));

// Round to nearest multiple of tile size
return Maths.Max(tileSize, (resolution / tileSize) * tileSize);
}

private static Double3 GetSunDirection(IReadOnlyList<IRenderableLight> lights)
{
if (lights.Count > 0 && lights[0] is IRenderableLight light && light.GetLightType() == LightType.Directional)
Expand All @@ -778,7 +801,7 @@ private static void RenderGizmos(CameraSnapshot css)
{
Double4x4 vp = Maths.Mul(css.projection, css.view);
(Mesh? wire, Mesh? solid) = Debug.GetGizmoDrawData(CAMERA_RELATIVE, css.cameraPosition);

if (wire != null || solid != null)
{
// The vertices have already been transformed by the gizmo system to be camera relative (if needed) so we just need to draw them
Expand Down
1 change: 1 addition & 0 deletions Prowl.Runtime/Resources/DefaultAssets.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ public enum DefaultShader
Tonemapper,
TAA,
SSR,
FXAA,
Bloom,
BokehDoF,
GBufferCombine,
Expand Down
1 change: 1 addition & 0 deletions Prowl.Runtime/Resources/Shader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ public static Shader LoadDefault(DefaultShader shader)
DefaultShader.Tonemapper => "Tonemapper.shader",
DefaultShader.TAA => "TAA.shader",
DefaultShader.SSR => "SSR.shader",
DefaultShader.FXAA => "FXAA.shader",
DefaultShader.Bloom => "Bloom.shader",
DefaultShader.BokehDoF => "BokehDoF.shader",
DefaultShader.GBufferCombine => "GBuffercombine.shader",
Expand Down
Loading