Растительность: ветер и материал листьев
Полный шейдер, маска ветра в цветах вершин, отсечение альфы, границы и диагностика.
Этот пример добавляет ветер и отсечение прозрачных участков листа к готовой 3D-модели. Он не создаёт геометрию дерева. Для рабочего ассета нужны нормальные ствол, ветви, листва, UV, материалы и уровни детализации; шейдер не исправит плохую модель или освещение.
Подготовьте модель
- Разделите кору и листву на поверхности с отдельными материалами. Коре обычно нужен непрозрачный материал; текстура листьев должна содержать полезную альфу.
- В редакторе модели покрасьте красный канал цвета вершин: 0 у закреплённых точек, плавно до 1 у подвижных концов. Чёрный удерживает вершину; белый даёт полную амплитуду. UV и цвет вершин — разные данные.
- Экспортируйте геометрию с UV, нормалями и цветами вершин. После импорта проверьте реальное движение: если цвета потеряны, вся поверхность может колебаться одинаково. Для диагностики временно используйте
wind_amplitude = 0.3и затем верните небольшое значение. - Назначайте материал листвы только её поверхности. MaterialOverride у MeshInstance3D перекрывает материалы всех поверхностей: так легко случайно превратить кору в листья. Для отдельной поверхности используйте SetSurfaceOverrideMaterial с её индексом.
Для начала возьмите одну ветку с листьями, направленный свет и нейтральное окружение. Убедитесь, что геометрия и текстура выглядят правильно без ветра. Масштаб модели должен соответствовать проекту.
Полный шейдер листьев
Создайте Shader с этим кодом и ShaderMaterial, который его использует. Назначьте RGBA-текстуру в leaf_albedo. Ветер смещает вершины в мировой плоскости XZ, а не вдоль локальных осей дерева.
shader_type spatial;
render_mode cull_disabled, world_vertex_coords;
uniform sampler2D leaf_albedo : source_color, hint_default_white, filter_linear_mipmap, repeat_disable;
uniform float wind_amplitude : hint_range(0.0, 1.0) = 0.06;
uniform vec2 wind_direction = vec2(1.0, 0.0);
uniform float wind_frequency = 0.8;
uniform float wind_speed = 1.2;
uniform float wind_phase = 0.0;
uniform float alpha_cutoff : hint_range(0.0, 1.0) = 0.5;
uniform float backlight_strength : hint_range(0.0, 1.0) = 0.1;
void vertex() {
vec2 direction = wind_direction / max(length(wind_direction), 0.0001);
float phase = dot(VERTEX.xz, vec2(0.8, 0.6)) * wind_frequency;
float weight = clamp(COLOR.r, 0.0, 1.0);
float offset = sin(phase + TIME * wind_speed + wind_phase) * wind_amplitude * weight;
VERTEX.xz += direction * offset;
}
void fragment() {
vec4 leaf = texture(leaf_albedo, UV);
ALBEDO = leaf.rgb;
ALPHA = leaf.a;
ALPHA_SCISSOR_THRESHOLD = alpha_cutoff;
ROUGHNESS = 1.0;
BACKLIGHT = leaf.rgb * backlight_strength;
}COLOR.r— только маска подвижности; он не умножается на ALBEDO и не окрашивает листья в красный.wind_amplitudeзадаёт максимальное смещение в мировых единицах при весе 1. Длина wind_direction не усиливает движение; нулевое направление его отключает.wind_frequencyменяет пространственную частоту. Это множитель координат в фазе синуса, а не длина волны в метрах.wind_speedменяет скорость фазы, wind_phase — её сдвиг в радианах. Для проверки замрите время ветра через wind_speed = 0; при wind_frequency = 0 фазы +π/2 и −π/2 дают противоположные крайние положения.ALPHA_SCISSOR_THRESHOLDвырезает прозрачные области. Это жёсткий порог, не полупрозрачность стекла. Мягкие края требуют отдельного решения и проверки выбранного сглаживания.cull_disabledпоказывает обе стороны. В текущем Forward+ нормаль обратной стороны уже разворачивается до пользовательского fragment; повторноеNORMAL = -NORMALиспортит освещение.BACKLIGHTдобавляет простой вклад света с обратной стороны. Это художественная настройка, не физическая модель толщины листа. Начинайте с малого значения.
Для крупного изгиба нужны корректировка нормалей и согласованная анимация ветвей. Здесь нормали не пересчитываются по деформированной поверхности, поэтому держите смещение небольшим. Одинаковое движение всей кроны не заменяет отдельные уровни движения ствола, ветвей и листьев.
Создание материала из C#
Этот вспомогательный метод принимает готовые Shader и Texture2D. Он проверяет тип шейдера и назначает текстуру по точному имени uniform.
using System;
using Reckfell;
public static class FoliageMaterial
{
public static ShaderMaterial Create(Shader shader, Texture2D leafTexture)
{
ArgumentNullException.ThrowIfNull(shader);
ArgumentNullException.ThrowIfNull(leafTexture);
if (shader.GetMode() != Shader.Mode.Spatial)
throw new ArgumentException("A spatial leaf shader is required.", nameof(shader));
var material = new ShaderMaterial { Shader = shader };
material.SetShaderParameter("leaf_albedo", leafTexture);
return material;
}
}Результат FoliageMaterial.Create(shader, texture) назначьте в материал поверхности листвы. Для MeshInstance3D, содержащего только листву, допустим MaterialOverride. Пути загрузки зависят от вашего проекта: не копируйте выдуманные пути к ассетам. Shader.GetMode проверяет объявленный тип, но не доказывает успешную компиляцию GPU-кода.
Границы, свет и цена кадра
GPU-деформация не меняет AABB исходного Mesh и автоматически не обновляет коллизию. Без запаса в границах листва может исчезать у края камеры или в теневом проходе. Увеличьте ExtraCullMargin либо задайте CustomAabb, который покрывает все положения. В текущей реализации запас применяется к локальным границам до трансформации: при единичном масштабе запас не меньше wind_amplitude покрывает смещение этого примера; при уменьшенном или неравномерном масштабе пересчитайте локальный запас. Не раздувайте границы всего леса без измерения стоимости отсечения.
Проверьте тень на земле при движении ветки. Для корректной демонстрации используйте динамический источник с включёнными тенями; статическая запечённая тень не будет повторять колебания. Этот проход проверок отдельно не подтверждает качество теней или запекания.
Начните с небольшого количества растений. Большие перекрывающиеся карточки листвы увеличивают число обрабатываемых пикселей; двусторонность и тени тоже имеют цену. Уменьшайте пустую площадь карточек, готовьте LOD и проверяйте время GPU на целевом устройстве. MultiMesh уменьшает накладные расходы большого числа экземпляров, но не устраняет стоимость самих листьев, прозрачных областей и теней.
Если результат неправильный
| Симптом | Что проверить |
|---|---|
| Белые прямоугольники вместо листьев | Назначена ли RGBA-текстура в leaf_albedo; есть ли альфа и корректные UV |
| Двигаются корни или весь объект одинаково | Красный канал вершин и сохранение цветов при экспорте |
| Ветер идёт поперёк ожидаемого направления | wind_direction задан в мировых XZ; поворот объекта не поворачивает направление ветра |
| Листва исчезает у края экрана | Границы с учётом максимальной деформации, масштаб и параметры видимости |
| Обратная сторона неожиданно темнее | Дублирование разворота NORMAL, направление света и BACKLIGHT; двусторонность сама по себе не добавляет свет |
| Мерцание силуэта вдали | Mipmaps и фильтрация текстуры, порог альфы, LOD и сглаживание |
| Кора стала прозрачной | Материал назначен через общий MaterialOverride вместо поверхности листвы |
Проверено на текущем Windows Forward+/Vulkan: полный шейдер и C#-метод, отсечение альфы, обе стороны, маски 0/0.5/1, ограничение маски, направление и фаза, реальное движение TIME, градиент и мировое смещение при переносе, повороте и неравномерном масштабе. Малые тестовые карточки не являются изображениями растений или демонстрацией качества графики. Импорт настоящей модели, интерфейс редактора, нормали при сильном изгибе, качество теней, лес с LOD, производительность и другие бэкенды отдельно не проверялись.
Смежные задачи: пространственные шейдеры, язык шейдеров, MultiMesh, GeometryInstance3D.