Сглаживание 3D: MSAA, FXAA, SMAA и TAA
Выбрать доступный метод, запустить сравнение краёв и не принять сохранённое свойство за применённый эффект.
Определить вид артефакта
Ступеньки по краю меша, мерцание тонкой геометрии и блики от normal map требуют разных решений. MSAA помогает геометрическим краям, но не сглаживает автоматически все детали шейдера внутри полигона. Экранные методы обрабатывают готовое изображение; временные используют историю кадров.
Сначала сравнивайте один метод на неизменной геометрии, камере и выходном разрешении. Затем проверяйте движение и целевые ассеты: красивый статический край не доказывает отсутствие ghosting или мерцания растительности.
Запустить полный пример
Сначала выполните настройку C#. Для примера нужны SDK 1.0.0, net10.0 и уровень .rscene с Entity3D-корнем. Сохраните оба файла ниже, выполните dotnet build и назначьте QualityLab3D.cs корню. Меши, камера и синий 2D-индикатор создаются кодом.
До запуска выберите экспортированное Preset: Native или Msaa4 для первого сравнения. Остальные варианты зависят от renderer. В примере настройка читается в _Ready; изменение Preset во время игры само по себе не вызывает 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.");
}
}Из собственного меню вызывайте QualityPreset.Apply(viewport, mode). Метод отклоняет неподдержанный режим до изменения настроек и возвращает false; текущее состояние сохраняется. true означает выполненную настройку для выбранного renderer, а не измерение возможностей любого GPU.
Сравнить реальные края
Оригинальные кадры 512×384 текущей локальной сборки 1.0.0. На границах с MSAA появились частично покрытые пиксели; проверка исключает область синего 2D-индикатора. Изображения не увеличены фильтром и не являются скриншотами редактора.
Выбрать метод по задаче
| Метод | Что делает | Поддержка и проверка |
|---|---|---|
| Msaa3D=Msaa4X | Несколько samples для покрытия геометрического края | Реальные края проверены в Compatibility/OpenGL и Forward+/Vulkan; поддержка samples зависит от GPU |
| ScreenSpaceAA=Fxaa | Быстрый экранный фильтр | Текущий API допускает Forward+/Mobile; реальный кадр проверен в Forward+ |
| ScreenSpaceAA=Smaa | Экранная обработка контуров | Текущий API допускает Forward+/Mobile; реальный кадр проверен в Forward+ |
| UseTaa=true | Временное накопление/реконструкция | Только Forward+; проверен статический край после накопления истории |
| Повышенный Scaling3DScale | Рендер выше выходного разрешения с уменьшением | Проверено отдельно на scale 2; увеличивает внутреннее число пикселей |
MSAA не требует истории, но расходует дополнительные буферы и обработку samples. FXAA может смягчить мелкие детали. Для TAA проверяйте движение, прозрачность, следы за объектами и смену камеры. В этом примере такая динамическая проверка не выполняется.
Для текстурного и бликового aliasing отдельно проверяйте mipmaps, фильтрацию, normal maps и roughness. Без анализа причины добавление ещё одного AA не гарантирует улучшения.
Не доверять одному getter
В текущем Compatibility присваивание ScreenSpaceAA=Fxaa сохранилось в C#-объекте, но renderer отклонил запрос, и кадр остался прежним. Аналогично UseTaa читался как true без TAA. Предупреждение движка и реальный результат важнее сохранённого значения свойства.
GetCurrentRenderingMethod возвращает gl_compatibility, forward_plus или другой текущий метод. Пример использует это для проверки доступности. MSAA 4× подтверждён на текущем ПК; наличие enum Msaa8X не доказывает поддержку любого количества samples любым устройством.
При FSR2 или другом временном upscaler не добавляйте отдельное TAA автоматически: текущий renderer отключает его внутри. QualityPreset выбирает методы раздельно и сбрасывает предыдущие настройки перед поддержанным новым режимом.
Диагностика
| Симптом | Проверка |
|---|---|
| FXAA/TAA включены, кадр тот же | Renderer, предупреждения и результат Apply |
| Края меша лучше, блики всё ещё мерцают | Текстуры/normal maps, roughness и поведение в движении |
| Весь кадр стал мягче | Экранный AA и пониженный resolution scale по отдельности |
| Следы за движущимся объектом | Временной метод и история; сравнить с MSAA/Native |
| Сглаживание изменило интерфейс | Отличать 3D AA от отдельного 2D-rendering/UI пути |