3D antialiasing: MSAA, FXAA, SMAA and TAA
Choose an available method, compare rendered edges and distinguish a stored setting from an applied effect.
Identify the artifact
Mesh-edge steps, thin-geometry flicker and normal-map highlights need different solutions. MSAA helps geometry edges but does not automatically smooth every shader detail inside a polygon. Screen-space methods process the image; temporal methods use frame history.
Compare one method with unchanged geometry, camera and output resolution first. Then test movement and target assets: a clean static edge does not prove ghosting-free motion or stable foliage.
Run the complete example
Complete C# setup first. The example needs SDK 1.0.0, net10.0 and an Entity3D-rooted .rscene level. Save both files below, run dotnet build and attach QualityLab3D.cs to the root. Code creates meshes, camera and the blue 2D indicator.
Before startup, select exported Preset: Native or Msaa4 for the first comparison. Other choices depend on the renderer. The example reads this setting in _Ready; editing Preset during play does not automatically call Apply.
QualityPreset.cs
using Reckfell;
public static class QualityPreset
{
public enum Mode { Native, Msaa4, Fxaa, Smaa, Taa, HalfBilinear, HalfNearest, DoubleBilinear, FsrHalf, Fsr2Half }
public static bool Apply(Viewport viewport, Mode mode)
{
if (!System.Enum.IsDefined(mode)) return false;
string renderer = RenderingServer.GetCurrentRenderingMethod();
bool forward = renderer == "forward_plus";
if ((mode == Mode.Fxaa || mode == Mode.Smaa) && renderer == "gl_compatibility") return false;
if ((mode == Mode.Taa || mode == Mode.FsrHalf || mode == Mode.Fsr2Half) && !forward) return false;
viewport.Msaa3D = Viewport.Msaa.Disabled;
viewport.ScreenSpaceAA = Viewport.ScreenSpaceAAEnum.Disabled;
if (forward) viewport.UseTaa = false;
viewport.Scaling3DMode = Viewport.Scaling3DModeEnum.Bilinear;
viewport.Scaling3DScale = 1;
switch (mode)
{
case Mode.Msaa4: viewport.Msaa3D = Viewport.Msaa.Msaa4X; break;
case Mode.Fxaa: viewport.ScreenSpaceAA = Viewport.ScreenSpaceAAEnum.Fxaa; break;
case Mode.Smaa: viewport.ScreenSpaceAA = Viewport.ScreenSpaceAAEnum.Smaa; break;
case Mode.Taa: viewport.UseTaa = true; break;
case Mode.HalfBilinear: viewport.Scaling3DScale = 0.5f; break;
case Mode.HalfNearest:
viewport.Scaling3DMode = Viewport.Scaling3DModeEnum.Nearest;
viewport.Scaling3DScale = 0.5f;
break;
case Mode.DoubleBilinear: viewport.Scaling3DScale = 2; break;
case Mode.FsrHalf:
viewport.Scaling3DMode = Viewport.Scaling3DModeEnum.Fsr;
viewport.Scaling3DScale = 0.5f;
break;
case Mode.Fsr2Half:
viewport.Scaling3DMode = Viewport.Scaling3DModeEnum.Fsr2;
viewport.Scaling3DScale = 0.5f;
break;
}
return true;
}
}QualityLab3D.cs
using Reckfell;
public partial class QualityLab3D : Entity3D
{
[Export] public QualityPreset.Mode Preset { get; set; } = QualityPreset.Mode.Native;
public Entity3D Geometry { get; private set; } = null!;
public Camera3D Camera { get; private set; } = null!;
public override void _Ready()
{
Geometry = new Entity3D();AddChild(Geometry);
var white = new StandardMaterial3D
{
AlbedoColor = Colors.White,
ShadingMode = BaseMaterial3D.ShadingModeEnum.Unshaded
};
for (int i = 0; i < 7; i++)
Geometry.AddChild(new MeshInstance3D
{
Mesh = new BoxMesh { Size = new Vector3(0.035f + i * 0.025f, 3.5f, 0.1f) },
Position = new Vector3(-2.1f + i * 0.7f, 0, 0),
RotationDegrees = new Vector3(0, 0, 25),
MaterialOverride = white
});
AddChild(new WorldEnvironment
{
Environment = new Reckfell.Environment
{
BackgroundMode = Reckfell.Environment.BGMode.Color,
BackgroundColor = Colors.Black,
AmbientLightSource = Reckfell.Environment.AmbientSource.Disabled,
ReflectedLightSource = Reckfell.Environment.ReflectionSource.Disabled
}
});
Camera = new Camera3D
{
Current = true,
Projection = Camera3D.ProjectionType.Orthogonal,
Size = 6,
Position = new Vector3(0, 0, 5)
};
AddChild(Camera);
var canvas = new CanvasLayer();AddChild(canvas);
canvas.AddChild(new ColorRect
{
Position = new Vector2(8, 8), Size = new Vector2(80, 20),
Color = new Color(0.08f, 0.25f, 1), MouseFilter = Control.MouseFilterEnum.Ignore
});
if (!QualityPreset.Apply(GetViewport(), Preset))
ReckfellApi.PushWarning("The selected quality preset is unavailable in this renderer; viewport settings were preserved.");
}
}From your own menu, call QualityPreset.Apply(viewport, mode). Unsupported choices return false before changing settings, preserving the current configuration. true means the renderer-specific configuration was performed, not that every GPU capability was measured.
Compare actual edges
Original 512×384 frames from the current local 1.0.0 build. MSAA adds partial-coverage edge pixels; checks exclude the blue 2D indicator region. These images are not filter-enlarged or editor screenshots.
Choose by task
| Method | Purpose | Support and verification |
|---|---|---|
| Msaa3D=Msaa4X | Multiple samples for geometric edge coverage | Actual edges verified in Compatibility/OpenGL and Forward+/Vulkan; sample support depends on GPU |
| ScreenSpaceAA=Fxaa | Fast screen filter | Current API permits Forward+/Mobile; actual output verified in Forward+ |
| ScreenSpaceAA=Smaa | Screen-space contour processing | Current API permits Forward+/Mobile; actual output verified in Forward+ |
| UseTaa=true | Temporal accumulation/reconstruction | Forward+ only; static edges verified after history accumulation |
| Increased Scaling3DScale | Render above output resolution and downsample | Separately verified at scale 2; increases internal pixel count |
MSAA needs no history but adds sample buffers and processing. FXAA can soften fine detail. For TAA, inspect motion, transparency, trails and camera changes. This example does not verify those dynamic cases.
For texture/specular aliasing, inspect mipmaps, filtering, normal maps and roughness separately. Adding another AA method without identifying the cause does not guarantee improvement.
Do not rely on one getter
Current Compatibility stores ScreenSpaceAA=Fxaa in the C# object while the renderer rejects it and output stays unchanged. UseTaa likewise reads true without actual TAA. Engine warnings and rendered output matter more than the stored property.
GetCurrentRenderingMethod returns gl_compatibility, forward_plus or another current method. The example uses it for availability checks. MSAA 4× is verified on this PC; the presence of Msaa8X does not prove every device supports any sample count.
Do not automatically combine FSR2 or another temporal upscaler with separate TAA: the current renderer disables separate TAA internally. QualityPreset selects methods independently and resets previous settings before a supported new choice.
Diagnosis
| Symptom | Check |
|---|---|
| FXAA/TAA appear enabled but output is unchanged | Renderer, warnings and Apply result |
| Mesh edges improve but highlights still flicker | Textures/normal maps, roughness and movement |
| Entire image becomes softer | Screen AA and reduced resolution scale separately |
| Trails behind moving objects | Temporal method/history; compare MSAA/Native |
| UI changes with AA | Distinguish 3D AA from separate 2D rendering/UI paths |