Fog: плотность, SDF и объёмный туман
Полные шейдеры, настройка FogVolume, координаты, свечение и диагностика границ.
Fog-шейдер задаёт вклад FogVolume в объёмный туман. Он выполняет fog() для ячеек объёмной сетки, а не рисует прозрачную поверхность. В текущем движке FogVolume виден только в Forward+ при включённом Environment.VolumetricFogEnabled. Обычный Environment.FogEnabled — отдельный эффект и не заменяет эту настройку.
Первый работающий объём
- Используйте Forward+ и активную Camera3D.
- В WorldEnvironment назначьте Environment, включите VolumetricFogEnabled. Для проверки локального объёма оставьте VolumetricFogDensity = 0.
- Добавьте FogVolume перед камерой, задайте ненулевой Size и Material = ShaderMaterial.
- В 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 создаёт проверенные окружение и объём:
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-проверки; соответствующие контракты сверены по исходникам.