Растительность вблизи камеры
Группа «Растительность» (V01: рекомендации 1, 3, 4 из docs/archive/plan/groups-top-level.md, группа 5; V02 — деревья по
маске леса 10 м). Травинки вокруг камеры (GrassField) и расстановка деревьев/импостеров по кромке леса.
Файлы остаются в scripts/terrain/; основной конфиг деревьев — configs/world.json → trees (группа «Рельеф»),
добавка V02 — configs/vegetation.json → trees (её ключи поверх, TreePlacer.with_vegetation).
Файлы
| Файл | Что делает |
|---|---|
scripts/terrain/grass_field.gd (GrassField, MultiMeshInstance3D) | сетка пучков травы вокруг камеры, приминание у ног пилота, посадки-прямоугольники, тестируемые статические формулы (far_density, press_factor, in_landing_rect, …) |
scripts/terrain/grass.gdshader | где трава (по карте поверхности), высота, прореживание/расширение вдали, ветер (terrain_wind.gdshaderinc), приминание, цвет — цвет земли луга в этой точке (meadow_look.gdshaderinc), скошенные площадки |
scripts/terrain/meadow_look.gdshaderinc | цвет луга, общий для рельефа и травинок: палитра/сухость локации, влажность и экспозиция из полей рельефа, пятна с высоты (meadow_macro: оттенок, выгоревшие пятна, покосы) — трава вблизи того же тона, что земля под ней и луг с высоты |
configs/vegetation.json | параметры травы (grass): свой файл, не делится с configs/world.json (владение группы «Рельеф») |
tests/vegetation/test_grass_field.gd | прореживание вдали, приминание по высоте ног, прямоугольник посадки |
scripts/terrain/tree_placer.gd (TreePlacer) | расстановка деревьев-моделей: маска 10 м, опушка, породы, утопление |
scripts/terrain/terrain_tree_models.gd (TerrainTreeModels) | MultiMesh моделей пород × LOD, пересчёт в рабочем потоке |
scripts/terrain/forest_impostors.gd + .gdshader (ForestImpostors) | билборды среднего плана (0,35–3,5 км) по той же маске |
tests/vegetation/test_trees_mask.gd | деревья на лесе маски, не в лугу, просеки, опушка гуще и шире |
tests/vegetation/test_trees_water.gd | на всех локациях ни модели, ни импостеры не стоят в воде (G маски), лес у берега остаётся; водоёмы OSM — замкнутые кольца с островами |
API
Terrain.get_grass_palette()— общая с рельефом палитра (цвет, сухость), читаетGrassField.Terrain.set_pilot(node)— трава приминается у ног нодыnode.GrassField.set_landing_sites(sites: Array[Dictionary])— посадки прямоугольником, не кругом:sites— какWorldObjects.get_landing_sites():[{position: Vector3, axis_deg, length_m, width_m, mowed_height_k?}]. Подключает группа «Сцена игры» (WorldObjects.get_landing_sites()→terrain.grass.set_landing_sites(...)). До вызова действует круговое скашивание вокругTerrain.get_landing_sites()радиусомgrass.landing_mow_radius_m.
Деревья по кромке маски 10 м (V02, VR-21, VR-6)
Terrainпосле создания деревьев передаёт маску:TerrainTreeModels.set_forest_mask(image, origin, cell_m)иForestImpostors.set_forest_mask(...)(Terrain.get_forest_mask(); origin — угол пикселя (0, 0)). Дерево стоит, где доля леса (R маски, билинейно между центрами пикселей) ≥ 0,5 — та же кромка, что у полога рельефа иTerrain.forest_at. Без маски (рантайм-локации) — по классу «лес» карты 25 м, как раньше. И не на воде: доля воды (канал G маски — реки/озёра OSM, та же вода, что рисует рельеф) меньшеvegetation.json → trees.water_max(0,35; кромка воды в шейдере — 0,5). WorldCover часто зовёт лесом кроны над руслом и острова, поэтому без этого деревья стояли «в воде». Вода — тоже кромка: у берега деревья гуще и утоплены меньше, как на опушке. Острова (role=inner) мультиполигонов OSM вычитаются из воды, внешние контуры собираются из участков (tools/osm/fetch_osm.py → join_rings; раньше каждый участок замыкался хордой и заливал пойму водой прямо по лесу). Импостеры берут ту же текстуру маски, что рельеф (без второй копии в видеопамяти).- Опушка: расстояние до кромки — кольца по 8 направлений (0,25…1,4 ×
edge_band_m, ближайший пиксель). В полосеedge_band_mдоля клеток с деревом растёт отtrees.densityдоedge_density, в клетке появляется второе дерево-модель (edge_extra), крона шире наedge_scale_k(высота прежняя — чтобы кроны меньше нависали над лугом при взгляде сверху). Утопление — отedge_sink_fractionу кромки доsink_fractionнаedge_probe_mвглубь (DSM на опушке уже приподнят кронами). - Импостеры: те же правила в шейдере (4 направления на
edge_band_m/2 иedge_band_m); уouter_mрастворяются по одному (порог — хеш клетки) в полосеimpostors.dissolve_m, без кольца. Тени не отбрасывают (тени — только у моделей LOD0–1, до 180 м). CPU-повтор правил —present_positions()(для тестов). - Пересчёт расстановки моделей (радиус 500 м, рабочий поток): ≈ 130 мс против 95 мс по классу 25 м.
Замеры V02 (стенд T01, Онгудай, 1920×1080, до = T02 / после):
- Тест
test_trees_mask: 38 707 деревьев (5 916 моделей + импостеры до 1,5 км) — 100 % на доле леса ≥ 0,5, ни одного центра в лугу, на просеках — 0; у кромки (0–8 м) деревьев на площадь ×1,6 против глубины. - S3 (300 м): луг в 0–30 м от кромки темнее луга в 100 м — 7,2 → 6,7 % (без деревьев 2,1 %); 50 % перехода яркости лес→луг — в 1–2 м от кромки маски (и до, и после), переход 10→90 % 15 → 12,5 м.
- S5 (2 км): средняя яркость леса с импостерами / без деревьев на 3,1–3,7 км — 0,990–0,993 → 0,997–1,000 (скачок на границе импостеры/полог ≤ 0,3 %); лес/луг 1,073 при фото (a) 1,159 (−7 %).
- 120 м AGL, pitch −50°: тени моделей на лугу у опушки не добавляют темноты (с тенями и без — ±0,4 из 255); тёмная полоса 20–30 м — нависание крон при взгляде сверху, не тень.
- GPU деревьев (полный −
--no-trees, медиана 4 прогонов): S1 0,51 → 0,46 мс, S3 0,52 → 0,48 мс.
Параметры (configs/vegetation.json → trees, поверх world.json → trees)
edge_band_m(16) — полоса опушки;edge_density(1,0),edge_extra(0,5),edge_scale_k(0,15) — см. выше.edge_sink_fraction(0,15, в world.json 0,05) — утопление у самой кромки.impostors.dissolve_m(800) — полоса растворения билбордов уouter_m.
Параметры (configs/vegetation.json → grass)
radius_m,clump_spacing_m,blades_per_clump,segments,blade_width_m— форма и число пучков.blade_height_m,crop_height_m,shrub_density— высота по классу поверхности (луг/поле/кустарник).forest_density(0,4),forest_height_m(0,12–0,35 м),forest_shade(0,8) — трава под пологом леса (класс FOREST, SF-2): реже, ниже и темнее луга — тень полога (под кронами светлого леса ~10–30 % света открытого места, травяной ярус покрывает ~30–50 % земли; оценка, не замер). Цвет — палитра луга в этой точке ×forest_shade; у стволов не выкашивается.forest_density0 — травы в лесу нет.far_density_min,thin_start_k,thin_end_k— прореживание вдали (рекомендация 1): доля видимых пучков падает от 1.0 у камеры доfar_density_minна долеthin_end_kотradius_m(начиная сthin_start_k); пучок виден, еслиhash(cell) < density. По умолчанию даёт в 0–10 м от камеры вершин в ≈ 6–7 раз больше на единицу площади, чем в кольце 20–30 м.far_width_k— во столько раз шире дальние пучки (компенсирует прореживание визуально).press_radius_m,press_agl_full_m,press_agl_zero_m— приминание у ног пилота (рекомендация 3): полное при высоте ног над землёй ≤press_agl_full_m(0,4 м), ноль при ≥press_agl_zero_m(1,2 м); нормаль травинки не переворачивается на изнанке (cull_disabled+ явныйNORMAL, изнанка светитсяBACKLIGHT, а не темнеет).mowed_height_k,landing_mow_radius_m— круговое скашивание по умолчанию (см. API выше).dryness_add_by_location— сухость травинок сверх палитры локации, только для травы; сейчас пусто — травинки того же цвета, что земля.sway_hz,sway_amp,bend_amp,color_variation,max_agl_m— качание, разброс цвета, высота отключения;sway_hz_var— у каждого пучка своя частота (±25 %) и фаза, без синхронного «полотна».density_pct— густота травы, %: пучков на площадь ∝ густоте (шагclump_spacing_m / √(density_pct/100)), 0 — трава выключена. Задают пресеты графики (configs/game.json → graphics_presets.*.configs.vegetation: Низкое 0, Среднее 100, Высокое 150) и слайдер «Густота травы» в настройках (0–200 %, шаг 10,density_range_pct/density_step_pct; выбор пресета ставит его густоту, слайдер её переопределяет; применяется со следующей загрузки местности). Дальний слой (far.density_scaled = false) не меняется.- Травинки:
blade_width_m0,025 м (вдвое тоньше прежних 0,05),clump_spacing_m0,21 м (вдвое чаще прежних 0,3 при 100 %),far_width_k1,35 (прирост вдали вдвое меньше прежнего 1,7). - Цвет: травинка = цвет земли луга в своей точке (
meadow_ground_color+meadow_macro, те же поля рельефа —GrassField.set_relief) ×blade_shade(у корня / у кончика) ×blade_tint; редкие сухие пучки —dry_clump_share. Зеленее/суше — только черезterrain_look(вместе с землёй).
Замеры (V01, критерий приёмки)
- Перенос
grassизconfigs/world.jsonвconfigs/vegetation.jsonне меняет кадр (PSNR ≥ 45 дБ, тот же--autopilot --screenshot --time --camera, пока стенд T01 не готов). - Прореживание: вершин в кольце 20–30 м от камеры в 5–8 раз меньше на единицу площади, чем в 0–10 м
(
test_far_thinning_ratio, ratio ≈ 6,5). - Приминание: пилот на земле — полное (
press_factor≈ 1); в 1,5 м над землёй — 0 (test_press_full_at_low_agl,test_press_zero_above_1_2m). - Посадка прямоугольником: точка в 1 м от края внутри площадки — скошено, в 1 м за краем — нет
(
test_landing_rect_inside_and_outside). - Против солнца (
--look/камера сзади, yaw +180°): изнанка травинок не темнее лица > 15 % —BACKLIGHTвgrass.gdshader. - GPU травы (кадр с травой минус
--no-grassвterrain_preview.gd) ≤ 0,3 мс наradius_m30 м,clump_spacing_m0,3 м. - Тонкая частая трава (0,025 м, шаг 0,21 м): GPU травы (тот же способ,
--bench-static, Онгудай, 1918×1036, RTX 4070 SUPER, видеокарта делилась с другими прогонами — разброс большой, по p10): «Высокое» (150 %) ≈ 2,3–2,6 мс, «Среднее» (100 %) ≈ 2,0 мс, прежняя трава (HEAD, шаг 0,3) ≈ 1,7 мс — бюджет ~1 мс превышен, прежняя оценка 0,3 мс для нынешнего шейдера уже не верна.