reckfellENGINE

Fog: плотность, SDF и объёмный туман

Полные шейдеры, настройка FogVolume, координаты, свечение и диагностика границ.

На этой странице

Fog-шейдер задаёт вклад FogVolume в объёмный туман. Он выполняет fog() для ячеек объёмной сетки, а не рисует прозрачную поверхность. В текущем движке FogVolume виден только в Forward+ при включённом Environment.VolumetricFogEnabled. Обычный Environment.FogEnabled — отдельный эффект и не заменяет эту настройку.

Первый работающий объём

  1. Используйте Forward+ и активную Camera3D.
  2. В WorldEnvironment назначьте Environment, включите VolumetricFogEnabled. Для проверки локального объёма оставьте VolumetricFogDensity = 0.
  3. Добавьте FogVolume перед камерой, задайте ненулевой Size и Material = ShaderMaterial.
  4. В ShaderMaterial назначьте Shader с полным кодом ниже.

Пример светится зелёным, чтобы его было видно без источников света. Это диагностический материал; для обычного освещённого тумана задайте подходящий ALBEDO и уберите EMISSION.

Шейдер
shader_type fog;
uniform float density = 0.4;
uniform vec3 glow : source_color = vec3(0.0, 1.0, 0.0);
void fog() {
    DENSITY = density;
    ALBEDO = vec3(0.0);
    EMISSION = glow;
}

Полный помощник FogSetup.cs создаёт проверенные окружение и объём:

C#
using Reckfell;

public static class FogSetup
{
    public static Reckfell.Environment CreateEnvironment()
    {
        return new Reckfell.Environment
        {
            BackgroundMode = Reckfell.Environment.BGMode.Color,
            BackgroundColor = Colors.Black,
            TonemapMode = Reckfell.Environment.ToneMapper.Linear,
            FogEnabled = false,
            VolumetricFogEnabled = true,
            VolumetricFogDensity = 0,
            VolumetricFogLength = 12,
            VolumetricFogTemporalReprojectionEnabled = false,
        };
    }

    public static FogVolume CreateVolume(Shader shader)
    {
        return new FogVolume
        {
            Position = new Vector3(0, 0, -4),
            Size = new Vector3(4, 4, 4),
            Shape = RenderingServer.FogVolumeShape.Box,
            Material = new ShaderMaterial { Shader = shader },
        };
    }
}

Назначьте результат CreateEnvironment() свойству Environment у WorldEnvironment. Загрузите шейдер через ResourceLoader.Load<Shader>("asset://LocalFog.reckfellshader"), передайте его в CreateVolume() и добавьте объём в дерево. Окружение, объём и камера должны принадлежать одному World3D. Помощник ставит объём в (0,0,-4) для камеры у начала координат, смотрящей вдоль −Z.

В примере отключено временное перепроецирование, чтобы проверить отдельные изменения без истории кадров. В готовой сцене оно помогает снижать шум, но требует проверки следов за движущимися объёмами. Linear tonemapping и диагностическое свечение здесь не являются рекомендацией художественного оформления.

Снятие Material не отключает FogVolume: в текущем RD назначается стандартный материал с плотностью 1. Чтобы скрыть объём, используйте Visible=false; для собственного ShaderMaterial можно также задать нулевую плотность. Getter Material при этом возвращает null, а не внутренний материал рендерера.

Плотность, цвет и свечение

DENSITY — плотность среды, не alpha прозрачной сетки. Положительное значение добавляет плотность, нулевое убирает вклад этого шейдера, отрицательное вычитает плотность в перекрытии. Явно присваивайте DENSITY: шейдер с одним EMISSION ничего не добавляет в проверенном RD-пути.

ALBEDO задаёт рассеяние входящего света. Чёрный ALBEDO без EMISSION даёт поглощающий объём; чтобы увидеть цветной ALBEDO, нужно освещение. EMISSION добавляет собственный светящийся цвет среды, но не создаёт Light3D для соседних моделей.

Отрицательная плотность не стирает чужое свечение. RD накапливает плотность и emission отдельно. Проверка показывает, что отрицательный объём убирает поглощение перед белым фоном, но не обнуляет emission другого положительного объёма. Для отверстия без свечения примените нужную маску и к DENSITY, и к EMISSION исходного шейдера либо перестройте объёмы.

Внутреннее накопление квантовано и ограничено; в текущем RD отдельный вклад emission умножается на ограниченную положительную плотность и ограничивается перед упаковкой. Экстремальные значения не заменяют корректные плотность, освещение и экспозицию. Несколько объёмов накапливают вклад, а не рисуются по порядку как полупрозрачные полигоны.

SDF — скаляр и ответственность за границу

SDF имеет тип float: отрицателен внутри формы и положителен снаружи. Это не vec3. Его значение вычисляется в пространстве формы объёма. Fog-шейдер вызывается для ячеек ограничивающего AABB, поэтому одного пересечения AABB недостаточно для точной формы.

Если шейдер не обращается к SDF, текущий RD автоматически применяет маску выбранной формы. При использовании SDF эта автоматическая маска отключается: шейдер должен сам ограничить DENSITY. Иначе эллипсоид может заполнить большую часть своего прямоугольного AABB.

Полный пример мягкой внутренней границы для локальной формы:

Шейдер
shader_type fog;
uniform float density = 0.4;
uniform float edge_width = 0.3;
void fog() {
    float weight = 1.0 - smoothstep(-max(edge_width, 0.001), 0.0, SDF);
    DENSITY = density * weight;
    ALBEDO = vec3(0.0);
    EMISSION = vec3(0.0, 1.0, 0.0);
}

Здесь edge_width задаёт ширину перехода в том же пространстве, что SDF; max защищает smoothstep от нулевой ширины. Пример проверен с Ellipsoid. Не переносите его бездумно на World: у глобальной формы нет такой локальной поверхности; используйте явную мировую маску.

Локальные и мировые координаты

Для локальных форм UVW — нормализованные координаты объёма, следующие за его преобразованием. WORLD_POSITION — мировая позиция ячейки, OBJECT_POSITION — мировая позиция центра объёма. Вычитание WORLD_POSITION − OBJECT_POSITION убирает перенос, но не отменяет поворот или масштаб.

Полный шейдер позволяет сравнить локальное разбиение с мировой осью X:

Шейдер
shader_type fog;
uniform bool world_space = false;
void fog() {
    float side = world_space ? WORLD_POSITION.x : UVW.x - 0.5;
    DENSITY = 0.4;
    ALBEDO = vec3(0.0);
    EMISSION = side >= 0.0 ? vec3(1.0, 0.0, 0.0) : vec3(0.0, 0.0, 1.0);
}

При world_space=false поворот FogVolume на π вокруг Y меняет красную и синюю половины местами. При true граница остаётся на мировой X=0. SIZE — полный размер локальной формы, не половина размера и не разрешение объёмной сетки.

В форме World UVW относится к объёмной сетке камеры; это не стабильные координаты текстуры локального объёма. Для неподвижного мирового шума используйте WORLD_POSITION с явным масштабом. Для локальной 3D-текстуры — UVW. Текстура плотности должна быть Texture3D; её красный канал можно использовать как множитель DENSITY.

Глобальный туман по высоте

Для этого полного примера выберите FogVolume.Shape = World. Локальный Size не ограничивает глобальную форму. Плотность постоянна ниже base_height и экспоненциально уменьшается выше него:

Шейдер
shader_type fog;
uniform float density = 0.2;
uniform float base_height = 0.0;
uniform float height_falloff = 1.0;
void fog() {
    float height = max(WORLD_POSITION.y - base_height, 0.0);
    DENSITY = density * exp(-max(height_falloff, 0.0) * height);
    ALBEDO = vec3(0.0);
    EMISSION = vec3(0.0, 0.0, 1.0);
}

Масштаб height_falloff относится к мировому Y, независимо от поворота локального объёма. Это осознанно другая настройка, чем затухание относительно OBJECT_POSITION.y встроенного FogMaterial. Значение 0 отключает высотное ослабление; отрицательные значения в примере приводятся к 0.

Встроенные значения

TIME и математические константы доступны глобально; остальные значения ниже относятся к fog(). in — только чтение, out — записываемый результат. У fog нет собственных blend-режимов CanvasItem.

Тип и имяЗначение
in float TIMEВремя рендерера с Engine.TimeScale; пауза дерева его сама по себе не останавливает. Период сброса rendering/limits/time/time_rollover_secs, по умолчанию 3600 секунд.
in float PI / TAU / EМатематические константы.
in vec3 WORLD_POSITIONМировая позиция обрабатываемой ячейки.
in vec3 OBJECT_POSITIONМировая позиция центра FogVolume.
in vec3 UVWНормализованные координаты локальной формы; у World — координаты сетки камеры.
in vec3 SIZEПолный размер локальной формы; для World локальная граница не используется.
in float SDFЗнаковое расстояние до формы; его использование отключает автоматическую маску в RD.
out float DENSITYВклад плотности, включая отрицательное вычитание. Присваивайте явно.
out vec3 ALBEDOЦвет рассеяния; влияет при наличии входящего света.
out vec3 EMISSIONСобственное свечение среды, не отдельный источник Light3D.

Детализация и встроенный FogMaterial

Froxel — ячейка объёмной сетки камеры, не пиксель экрана. Детали мельче сетки теряются, а положение камеры меняет их выборку. Environment.VolumetricFogLength задаёт дальность: увеличение охвата при том же числе ячеек уменьшает детализацию по глубине. Параметры проекта rendering/environment/volumetric_fog/volume_size и volume_depth увеличивают разрешение и стоимость. Сначала задайте нужный охват и размер деталей, затем подбирайте сетку; числа из тестовой сцены не являются готовым пресетом качества.

Если собственный алгоритм не нужен, используйте FogMaterial. Он уже умножает Density на высотное ослабление, красный канал DensityTexture и затухание границы. HeightFalloff и EdgeFade неотрицательны, но не ограничены единицей; Density может быть отрицательной. При texture=null встроенный sampler использует белую текстуру. Изменение поля FogMaterial не меняет одноимённый uniform произвольного ShaderMaterial.

Диагностика

СимптомЧто проверить
Объём не виденForward+, VolumetricFogEnabled, общий World3D, видимость, материал типа fog, положение и ненулевой Size.
Цвет ALBEDO чёрныйОсвещение, плотность и наличие поглощения; сравните с небольшим EMISSION для диагностики.
Есть EMISSION, но эффекта нетЯвное присваивание DENSITY и её ненулевой вклад.
После SDF форма стала прямоугольнойВерните собственную маску DENSITY по SDF.
Отрицательный объём оставил свечениеОн вычитает плотность, но не гарантирует вычитание чужого emission.
Шум поворачивается вместе с объёмомИспользуется локальный UVW; для мировой привязки нужен WORLD_POSITION.
Туман зернистый или полосатыйМасштаб деталей, дальность, разрешение сетки и перепроецирование.
За движением остаются следыИстория временного перепроецирования; сравните с выключенной историей.
Размер задан, но World заполняет сценуГлобальная форма не ограничена Size; нужна маска по мировым координатам.

Проверены четыре полных шейдера и помощник на Windows Forward+/Vulkan: цвет, отсутствие DENSITY, нулевая плотность, отрицательное перекрытие с поглощением и emission, SDF, координаты при повороте, глобальная высота и включение окружения. Проверки выполняют функциональные чтения пикселей, не публикуют художественные иллюстрации. Семантика TIME, Texture3D, все варианты света/GI, движение с историей, прочие платформы/экспорты и производительность не имеют здесь отдельной полной runtime-проверки; соответствующие контракты сверены по исходникам.

Документация Reckfell EngineНаверх ↑
БЫСТРЫЙ ПЕРЕХОДНачало работыEntity и иерархияC# & .NET 10Сцены и префабы
Руководства и полный API Reckfell K
reckfellENGINE
RECKFELL ACCOUNT

Вход в аккаунт

Войдите в аккаунт Reckfell.

Один аккаунт для всей платформы Reckfell.