Add fish-pipeline skill
This commit is contained in:
@@ -0,0 +1,268 @@
|
||||
---
|
||||
name: fish-pipeline
|
||||
description: >-
|
||||
Переносит рыбу из Blender в Unity с физическим скелетом, skinning и независимыми
|
||||
morph/shape-key анимациями. Использовать для FishGen, импорта GLB, ретопологии,
|
||||
настройки Rigidbody/ConfigurableJoint, BlendShape loop, исправления надувания и
|
||||
чрезмерных morph-деформаций. Адаптируется к модели и версиям API и обновляет
|
||||
собственные инструкции только после проверенных результатов.
|
||||
---
|
||||
|
||||
# Fish Pipeline
|
||||
|
||||
Перенос рыбы из Blender в Unity с двумя независимыми уровнями деформации:
|
||||
|
||||
- физический скелет двигает тело и хвост через `Rigidbody` и `ConfigurableJoint`;
|
||||
- morph-анимация меняет только `blendShape`-веса в собственном loop и не управляет костями.
|
||||
|
||||
Не считать задачу завершённой только по успешному импорту. Проверять геометрию,
|
||||
материалы, физику, morph-веса и фактическую деформацию через `BakeMesh`.
|
||||
|
||||
## Самообновление и адаптация
|
||||
|
||||
При каждом вызове:
|
||||
|
||||
1. Сначала читать этот файл и `references/experience.md`.
|
||||
2. Проверять версии Unity, `com.unity.cloud.gltfast`, `com.unity.pipeline`, Unity CLI и Blender.
|
||||
Использовать локальную документацию установленных пакетов и introspection API, а не память.
|
||||
3. Не копировать параметры прошлой рыбы вслепую. Определять по данным модели:
|
||||
- продольную ось и направление головы;
|
||||
- размеры и масштаб;
|
||||
- тип `Renderer`;
|
||||
- число костей и morph targets;
|
||||
- `GetBlendShapeFrameWeight` и диапазон значений кривых;
|
||||
- пути `AnimationClip` и привязки кривых.
|
||||
4. Если API отличается, сначала найти рабочий вызов через документацию или RNA/командную схему.
|
||||
5. После успешной проверки обновлять этот skill и `references/experience.md`:
|
||||
- записывать только подтверждённые симптомы, причины, исправления и способы проверки;
|
||||
- устаревшие инструкции заменять, а не накапливать противоречия;
|
||||
- неудачные попытки сохранять только как краткие предупреждения с итогом;
|
||||
- объединять повторы и поддерживать файл компактным.
|
||||
6. Приглашение пользователя к исправлению считать ошибкой pipeline. Сначала найти причину,
|
||||
затем менять код или настройки. Не маскировать ошибку простым уменьшением анимации.
|
||||
7. После подтверждения пользователем рабочего результата зафиксировать его как текущий baseline.
|
||||
|
||||
Ограничения самообновления:
|
||||
|
||||
- обновления выполняются только во время вызова skill и в рамках запроса пользователя;
|
||||
- не запускать фоновые процессы, goals или cron для самообновления;
|
||||
- не делать commit/push без явной просьбы;
|
||||
- не менять пользовательские модели и анимации без необходимости;
|
||||
- перед опасными изменениями сохранять исходники или делать отдельную рабочую копию;
|
||||
- не записывать секреты, токены и личные данные;
|
||||
- не отключать телеметрию и не менять настройки приватности.
|
||||
|
||||
## Текущий проверенный контур
|
||||
|
||||
- Unity: `6000.6.8f1`.
|
||||
- `com.unity.cloud.gltfast`: `6.20.0`.
|
||||
- `com.unity.pipeline`: `0.10.0-exp.1`.
|
||||
- Blender: `5.2.3 LTS`.
|
||||
- UPM-пакет: `/home/emil/Projects/Unity-tools/fish-gen`.
|
||||
- Тестовый Unity-проект: `/home/emil/Projects/FishingGame`.
|
||||
- Текущий модуль привязки: `fish-gen/Editor/FishModelBinder.cs`.
|
||||
- Текущая morph-модель: `fish-gen/Models/Mesh0Animated.glb`.
|
||||
|
||||
Это отправная точка, а не обязательные пути для других проектов. В другом проекте сначала
|
||||
определить его корень и расположение FishGen через `unity status` и Package Manager.
|
||||
|
||||
## 1. Подготовка модели в Blender
|
||||
|
||||
Сохранять исходник отдельно. Для игровой рыбы предпочтительна чистая топология и UV.
|
||||
|
||||
При ручной ретопологии:
|
||||
|
||||
- сохранять петли вдоль позвоночника и у оснований плавников;
|
||||
- строить низкополигональную поверхность поверх исходника;
|
||||
- использовать Shrinkwrap и перенос/запекание текстур;
|
||||
- проверять границы, non-manifold рёбра и тонкие плавники.
|
||||
|
||||
При автоматической ретопологии не считать Decimate достаточным для анимации. Если QuadriFlow
|
||||
отклоняет mesh, проверять manifold и нормали. Для сложной модели допустим Voxel/Sharp ReMesh,
|
||||
но результат проверять визуально и по топологии.
|
||||
|
||||
### Shape keys и маски
|
||||
|
||||
- `Basis` не редактировать.
|
||||
- Vertex Group ограничивает существующую деформацию, но не создаёт её.
|
||||
- Для движения глаз и рта создавать отдельные shape keys и плавные маски.
|
||||
- Для независимого morph-loop не использовать костную анимацию.
|
||||
- Проверять крайние значения ключей на пересечения и растяжения кожи.
|
||||
|
||||
При UV Data Transfer в Blender учитывать направление: для переноса из выделенных исходников
|
||||
в активный target использовать `use_reverse_transfer=True`. Предварительно проверять RNA:
|
||||
имена enum и поведение оператора зависят от версии. При ошибке восстанавливать исходник,
|
||||
а не продолжать с повреждёнными UV.
|
||||
|
||||
## 2. Экспорт GLB с morph-анимацией
|
||||
|
||||
Перед экспортом убедиться, что shape keys имеют кривые. Экспортировать только нужный объект.
|
||||
Пример для проверенной Blender-версии:
|
||||
|
||||
```python
|
||||
import bpy
|
||||
|
||||
bpy.ops.object.select_all(action='DESELECT')
|
||||
obj = bpy.data.objects["Mesh_0"]
|
||||
obj.select_set(True)
|
||||
bpy.context.view_layer.objects.active = obj
|
||||
|
||||
bpy.ops.export_scene.gltf(
|
||||
filepath="/home/emil/Projects/Unity-tools/fish-gen/Models/Mesh0Animated.glb",
|
||||
export_format='GLB',
|
||||
use_selection=True,
|
||||
export_animations=True,
|
||||
export_animation_mode='ACTIVE_ACTIONS',
|
||||
export_frame_range=True,
|
||||
export_force_sampling=True,
|
||||
export_morph=True,
|
||||
export_morph_animation=True,
|
||||
export_apply=False)
|
||||
```
|
||||
|
||||
Для другой модели заменить имя, путь и режим экспорта после чтения её action/NLA.
|
||||
Не запекать shape keys в mesh.
|
||||
|
||||
## 3. Импорт и проверка данных в Unity
|
||||
|
||||
Использовать `unity-cli`, `unity-pipeline` и `unity-package-management`, если они доступны.
|
||||
Пакеты добавлять через Package Manager, не редактировать `manifest.json` вручную.
|
||||
|
||||
```bash
|
||||
unity command script eval 'AssetDatabase.ImportAsset(
|
||||
"Packages/com.shanaty.fish-gen/Models/Mesh0Animated.glb",
|
||||
ImportAssetOptions.ForceUpdate); return "Imported";'
|
||||
```
|
||||
|
||||
Проверить:
|
||||
|
||||
- `SkinnedMeshRenderer` и материал;
|
||||
- `mesh.blendShapeCount` и имена форм;
|
||||
- наличие `AnimationClip`;
|
||||
- `AnimationUtility.GetCurveBindings(clip)`;
|
||||
- размеры mesh и направление головы.
|
||||
|
||||
glTFast импортирует morph-анимации, но их проигрывание и назначение путей требует
|
||||
отдельной настройки.
|
||||
|
||||
## 4. Физический скелет
|
||||
|
||||
Текущий рабочий режим Fish Editor: **Tools → Fishing → Fish Editor → Test Hanging**.
|
||||
|
||||
Для каждого сегмента:
|
||||
|
||||
- `Rigidbody`;
|
||||
- `CapsuleCollider` с фактическими размерами сегмента;
|
||||
- `ConfigurableJoint` с предыдущим сегментом;
|
||||
- ограничения углов, пружина и damping.
|
||||
|
||||
Первый сегмент соединять с kinematic `Mouth Anchor`. Не задавать непропорциональный
|
||||
`localScale` физическому телу: размер задаётся коллайдеру, а визуальный mesh остаётся
|
||||
отдельным дочерним объектом. Для короткой цепочки отключать self-collision между сегментами.
|
||||
|
||||
`Tail Impulse` должен прикладывать силу к кончику последнего Rigidbody через
|
||||
`AddForceAtPosition`, поочерёдно меняя направление.
|
||||
|
||||
## 5. Skinning без повреждения morph targets
|
||||
|
||||
Для текущей модели ось `+Y` соответствует голове, а скелет идёт от головы к хвосту по `+X`.
|
||||
Для неё используется поворот `Quaternion.Euler(0, 0, 90)`. Для другой рыбы сначала определить
|
||||
ориентацию — не применять этот поворот автоматически.
|
||||
|
||||
Базовые правила:
|
||||
|
||||
- позиции вершин: поворот, равномерный масштаб и перенос начала координат;
|
||||
- нормали и tangents: только поворот;
|
||||
- веса вершин: плавная смесь двух ближайших костей по продольной координате;
|
||||
- `bindposes[i] = bones[i].worldToLocalMatrix * visual.localToWorldMatrix`;
|
||||
- источник может быть как `MeshRenderer`, так и `SkinnedMeshRenderer`.
|
||||
|
||||
Для каждого morph target копировать все кадры через `GetBlendShapeFrameVertices` и
|
||||
`AddBlendShapeFrame`:
|
||||
|
||||
- `deltaVertices`: поворот и тот же равномерный масштаб, что у базовых вершин;
|
||||
- `deltaNormals` и `deltaTangents`: только поворот;
|
||||
- исходный `AnimationClip` не изменять.
|
||||
|
||||
### Два разных веса — не смешивать
|
||||
|
||||
**Morph-frame weight** и **animation-curve weight** — разные вещи.
|
||||
|
||||
В проверенном glTFast `GetBlendShapeFrameWeight` возвращал `1`, тогда Unity ожидал `100`.
|
||||
Это вызывало экстраполяцию и надувание. При переносе определять диапазон и нормализовать его
|
||||
к `0–100`. Если исходный диапазон `0–1`, умножать frame weight на `100`; если уже `0–100`,
|
||||
не умножать повторно.
|
||||
|
||||
Кривые morph-анимации также могут иметь диапазон `0–1`, тогда как Unity BlendShape использует
|
||||
`0–100`. Вычислять коэффициент по фактическим ключам. Текущий пользовательский уровень
|
||||
амплитуды — `20%` от исходной, но сначала исправлять нормализацию и только затем настраивать
|
||||
силу анимации.
|
||||
|
||||
## 6. Независимый morph-loop
|
||||
|
||||
Использовать `Animation` на том же GameObject, где находится `SkinnedMeshRenderer`.
|
||||
|
||||
Для каждого исходного клипа:
|
||||
|
||||
1. Создать новый `AnimationClip` с `legacy = true` и `wrapMode = Loop`.
|
||||
2. Копировать только кривые `SkinnedMeshRenderer` с `blendShape.*`.
|
||||
3. Transform-кривые исключать, чтобы анимация не боролась с физикой.
|
||||
4. Путь кривой заменить на `""`, поскольку renderer находится на том же объекте.
|
||||
5. Ключи копировать в новый `AnimationCurve`, не мутируя исходный клип.
|
||||
6. Установить кривую через `loopClip.SetCurve`.
|
||||
7. Добавить клип в `Animation`, включить loop и `playAutomatically`.
|
||||
|
||||
Исходный путь вида `Mesh_0` нельзя оставлять без проверки: он относится к иерархии Blender,
|
||||
а не к созданному `Fish Visual`.
|
||||
|
||||
## 7. Обязательная проверка
|
||||
|
||||
После изменения C#:
|
||||
|
||||
```bash
|
||||
unity command script recompile --project-path <project>
|
||||
unity command script recompile_status --project-path <project>
|
||||
```
|
||||
|
||||
Запустить `Test Hanging` и проверить:
|
||||
|
||||
- создание Rigidbody, joints, mesh и материалов;
|
||||
- `blendShapeCount` и имена форм;
|
||||
- `Animation.isPlaying`;
|
||||
- все кривые loop указывают на `""` и только на `blendShape.*`;
|
||||
- веса форм меняются во времени;
|
||||
- `Tail Impulse` двигает последний сегмент одновременно с morph-loop.
|
||||
|
||||
Сравнить `BakeMesh` до и после morph или импульса. Для текущего baseline при `Breath = 10`
|
||||
максимальное смещение вершины должно быть примерно **0.00178 м**, а не 0.17772 м.
|
||||
|
||||
Проверить консоль:
|
||||
|
||||
```bash
|
||||
unity command console status --project-path <project>
|
||||
unity command console get --level error --project-path <project>
|
||||
```
|
||||
|
||||
После теста остановить Play Mode, снять временный Run In Background override и удалить
|
||||
временные probe-скрипты и тестовые объекты.
|
||||
|
||||
## 8. Диагностика
|
||||
|
||||
| Симптом | Сначала проверить |
|
||||
|---|---|
|
||||
| Рыба надувается | `GetBlendShapeFrameWeight`, экстраполяцию и фактический `BakeMesh` |
|
||||
| Анимация почти незаметна | Диапазон ключей `0–1` против Unity `0–100` |
|
||||
| Глаза/рот не двигаются | `legacy`, `Animation.isPlaying`, имена форм и пути кривых |
|
||||
| Анимация ломает физику | Наличие transform-кривых вместо только `blendShape.*` |
|
||||
| Mesh внизу/боком | Ось модели, bindposes и направление головы |
|
||||
| Текстуры испорчены | UV, направление Data Transfer, активную UV-развёртку |
|
||||
| Суставы дрожат | Масштаб Rigidbody, массы, solver iterations и self-collision |
|
||||
|
||||
## 9. Фиксация результата
|
||||
|
||||
После успешной проверки:
|
||||
|
||||
- сохранить `.meta` вместе с ассетами;
|
||||
- обновить этот skill и `references/experience.md` по правилам выше;
|
||||
- убедиться, что временные probe-файлы удалены;
|
||||
- commit/push выполнять только по явному запросу пользователя.
|
||||
@@ -0,0 +1,60 @@
|
||||
# Проверенный опыт Fish Pipeline
|
||||
|
||||
Обновлять только после подтверждения. Повторяющиеся записи объединять. Устаревшие правила
|
||||
заменять в `SKILL.md`, а здесь сохранять краткую историю причин и проверок.
|
||||
|
||||
## 2026-11-09 — Базовый физический скелет
|
||||
|
||||
- Создана цепочка из 6 Rigidbody и 6 ConfigurableJoint с kinematic mouth anchor.
|
||||
- Нестабильность уменьшается, когда физический transform не масштабируется, а размер задаётся
|
||||
CapsuleCollider. Визуальная капсула находится в дочернем объекте.
|
||||
- Tail Impulse через `AddForceAtPosition` к кончику последнего сегмента успешно раскачивает цепь.
|
||||
|
||||
## 2026-11-09 — GLB и skinning
|
||||
|
||||
- `com.unity.cloud.gltfast` импортирует mesh, материалы, morph targets и AnimationClips.
|
||||
- Для первой модели 6 костей и 6194 вершины успешно деформировались после импульса.
|
||||
- Направление модели нужно определять отдельно; поворот `Z +90` подходит только для проверенной
|
||||
ориентации `head +Y` к `tail +X`.
|
||||
|
||||
## 2026-11-09 — Независимая morph-анимация
|
||||
|
||||
- Shape keys `Eye_Look_Left`, `Eye_Look_Right`, `Open_mouths`, `Breath` экспортированы из Blender
|
||||
с `export_morph=True`, `export_morph_animation=True`, `ACTIVE_ACTIONS`.
|
||||
- В Unity найден один 4-секундный клип с кривыми `blendShape.*`.
|
||||
- Для независимого loop создан новый legacy `AnimationClip`, путь кривой изменён на пустой,
|
||||
transform-кривые отброшены. Физика и morph-анимация работают одновременно.
|
||||
|
||||
## 2026-11-09 — Нормализация morph-frame
|
||||
|
||||
Симптом: рыба сильно надувалась, `Breath = 10` давало смещение вершины `0.17772 м`.
|
||||
|
||||
Причина: glTFast вернул `GetBlendShapeFrameWeight = 1`, тогда Unity ожидал диапазон `0–100`.
|
||||
Копирование значения `1` приводило к экстраполяции в 10 раз.
|
||||
|
||||
Исправление: нормализовать frame weight к `0–100` при создании target mesh.
|
||||
|
||||
Проверка после исправления: при `Breath = 10` максимальное смещение стало `0.00178 м`.
|
||||
Не уменьшать анимацию до такой проверки — сначала исправить frame weight.
|
||||
|
||||
## 2026-11-09 — Значения кривых
|
||||
|
||||
- Исходные morph-кривые имеют диапазон `0–1`, Unity BlendShape использует `0–100`.
|
||||
- При переносе ключи копируются в новый `AnimationCurve`; исходный клип не изменяется.
|
||||
- Текущая пользовательская сила воспроизведения — 20% от исходной амплитуды. Это настройка после
|
||||
исправления frame weight, а не решение проблемы надувания.
|
||||
|
||||
## 2026-11-09 — UV Data Transfer в Blender
|
||||
|
||||
- В Blender 5.2 направление оператора критично: для selected source → active target требуется
|
||||
`use_reverse_transfer=True`.
|
||||
- Неправильное направление повредило UV исходника в текущей сцене; источник восстановлен из
|
||||
отдельной сохранённой копии.
|
||||
- Перед вызовом читать RNA: допустимые enum для `layers_select_src/dst` зависят от контекста.
|
||||
|
||||
## 2026-11-09 — Ретопология
|
||||
|
||||
- Простой Decimate не признан подходящим для анимации этой рыбы.
|
||||
- QuadriFlow отклонил исходную поверхность; Voxel/Sharp ReMesh дал 16166 квадов и сохранил форму.
|
||||
- UV восстановлены через Data Transfer в правильном направлении. Для скелета нужна отдельная
|
||||
проверка non-manifold участков.
|
||||
Reference in New Issue
Block a user