兩個角色並排擺在畫面上,播同一段 T-pose 參考動畫。右邊 Quaternius 的黃色人偶站得筆直、雙手水平張開;左邊我花了一下午從 DAZ 搬過來的 Laura,卻是駝背、骨盆往後翹、兩隻手往正前方伸,像在夢遊。
這篇是我在 Godot 4.7 做「人體軟組織物理測試」的完整試錯紀錄。目標很單純:站著不動時看得到呼吸起伏,走路時胸部和臀部會隨步伐自然晃動、受重力影響。結果從程序化人偶、DAZ 模型匯入、Blender 補骨頭、Godot 的 SkeletonModifier3D,一路踩到動作捕捉重定向,每一關都有坑。我把每個坑的現象、原因跟最後的解法都寫下來,給之後要做 Godot 軟組織物理或骨架重定向的人少繞一點路。
測試環境
- Godot 4.7.1 stable(macOS,Forward+)
- Blender 5.2.2 LTS
- Diffeomorphic DAZ Importer 5.2.0
- Daz Studio 6、角色 Laura for Genesis 9
- 動作:Quaternius Universal Animation Library(Standard,CC0)
- 前置知識:GDScript、Godot 骨架節點、基本的 Blender 骨架操作
整條管線長這樣:
第一版:用程序化人偶驗證彈簧質點
我沒有一開始就碰真模型,先用膠囊和橢球拼一個人偶,把物理模型驗證完再說。軟組織的模型很經典:每塊組織是一個質點,用彈簧拉回「該在的位置」,用阻尼吸收相對速度,再加上重力。
綠點是目標位置,紅點是物理質點,右下角的波形是胸(紅)和臀(藍)的垂直位移。走路時兩條線會隨步伐規律振盪,物理這一層算是成立了。
積分器我選半隱式 Euler,也就是先更新速度、再用新速度更新位置。Gaffer On Games 的 Integration Basics 有清楚的比較:顯式 Euler 在彈簧系統上會越跑能量越多,最後炸掉;只是把兩行順序對調,系統就穩定了。為了讓剛性調高時也不爆,我再加上固定 1/240 秒的子步長,目標點在一幀內線性插值:
const SUBSTEP := 1.0 / 240.0
var steps := clampi(ceili(delta / SUBSTEP), 1, 32)
var h := delta / steps
var target_vel := (target - prev_target) / delta
for i in steps:
var t := prev_target.lerp(target, float(i + 1) / steps)
var force := -stiffness * (p - t) - damping * (v - target_vel)
v += (force / mass + gravity - rest_sag_comp) * h # 先更新速度
p += v * h # 再更新位置
# 超過皮膚/筋膜極限就截斷,並移除往外的速度分量
var d := p - t
if d.length() > max_offset:
var n := d.normalized()
p = t + n * max_offset
var vn := (v - target_vel).dot(n)
if vn > 0.0:
v -= n * vn
參數我用自然頻率和阻尼比來調,比直接調 k、c 直覺很多:
| 部位 | 剛性 k (N/m) | 阻尼 c (N·s/m) | 質量 m (kg) | 自然頻率 ω₀ | 阻尼比 ζ |
|---|---|---|---|---|---|
| 胸 | 180 | 3.5 | 0.4 | 3.4 Hz | 0.21 |
| 臀 | 420 | 9.0 | 0.8 | 3.6 Hz | 0.25 |
ω₀ = √(k/m) / 2π,ζ = c / (2√(km))。取值方式是先決定想要的手感:ω₀ 抓 3 到 4 Hz,大約是走路步頻(每秒 1.5 到 2 步)的兩倍左右,晃動才不會和腳步黏在一起;平衡下垂量 mg/k 控制在 2 公分左右,再回推 k 和 c。這組數字是看畫面調出來的手感值,不是量測人體得到的。臀部是肌肉為主,阻尼比給高一點,晃一兩下就停;胸部阻尼比低,會多回彈幾次。
這裡有個一開始沒想清楚的地方:模型本來就是在重力下雕出來的形狀。如果直接加重力,站著不動時組織會再往下垂一截,等於重複算了一次重力。對線性彈簧來說,固定的重力只會平移平衡點,不影響動態響應,所以我在初始化時把重力方向記在父骨空間,之後每幀抵銷掉:
# 初始化(角色直立)時記住重力在父骨空間的方向
gravity_in_parent = parent_basis.inverse() * gravity
# 每幀:抵銷量跟著身體一起轉
var comp := parent_basis * gravity_in_parent * gravity_scale
效果是站著不動時組織停在原位;跳起來騰空時,身體以 g 加速下墜,組織卻失去抵銷量的支撐,會相對往上浮,落地再往下甩;身體傾倒時,下垂方向也會跟著改變。這比「一直往下垂」更接近真實。
換成真模型:DAZ 到 Blender 的五個坑
人偶驗證完,該換真模型了。我用的是 Daz Studio 裡的 Laura for Genesis 9,透過 Blender 外掛 Diffeomorphic DAZ Importer 匯入。
授權先看清楚 Daz 商店素材的標準授權只涵蓋算圖成圖片或影片。要把 3D 模型放進遊戲或互動應用程式裡發佈,每個用到的商品都要另買 Interactive License 。自己做技術測試沒問題,要上架就得處理。
坑 1:Bitbucket 下載直接 402
Diffeomorphic 以前放在 Bitbucket,網路上很多教學還指向那裡。我用 API 去抓,回來的是 HTTP 402:
A workspace on a Free plan does not support uploading or downloading files.
原因是 Bitbucket 免費方案已經不提供檔案下載。專案現在搬到 GitHub,我裝的是 version_5_2_0 這個 tag ,作者部落格 也有 5.2.0 的發佈公告。直接從 GitHub 的 tag 下載就好。
坑 2:Blender 4.1 裝不了新版
我機器上原本是 Blender 4.1.1。新版 Diffeomorphic 是用 Blender extension 格式發佈,blender_manifest.toml 寫著 blender_version_min = "4.2.0",而 extension 系統本身就是 Blender 4.2 才有的。作者部落格說可以跑在 3.0 以上,但我不想賭舊式安裝會不會出包,直接升級到 Blender 5.2.2 LTS,再用指令安裝:
blender --command extension build --source-dir import_daz-version_5_2_0 --output-filepath import_daz-5.2.0.zip
blender --command extension install-file -r user_default -e import_daz-5.2.0.zip
坑 3:用腳本呼叫匯入,FINISHED 卻什麼都沒有
我是透過 Blender 的 Python(MCP)自動化匯入的。第一次直接呼叫 bpy.ops.daz.easy_import_daz 就噴錯:
AttributeError: 'Context' object has no attribute 'object'
這個匯入器需要 3D 視窗的 context,要用 temp_override 包起來。修好之後更詭異:回傳 {'FINISHED'}、耗時 0.0 秒,場景裡一個物件都沒有。翻原始碼才發現它讀的是 directory 加 files,不是 filepath,路徑沒對上就會靜默失敗。傻眼。
win = bpy.context.window_manager.windows[0]
area = next(a for a in win.screen.areas if a.type == 'VIEW_3D')
region = next(r for r in area.regions if r.type == 'WINDOW')
with bpy.context.temp_override(window=win, area=area, region=region):
bpy.ops.daz.easy_import_daz(
directory="/Volumes/SSD/DAZ 3D Library/People/Genesis 9/Characters/",
files=[{"name": "Laura for Genesis 9.duf"}],
fitMeshes='UNIQUE', # 沒有從 Daz Studio 匯出 .dbz 時用這個
materialMethod='FBX_COMPATIBLE', # 之後要匯出 glTF
useMergeMaterials=False, # 見坑 4
)
坑 4:全身只剩一個叫 Fingernails 的材質
第一次匯入的是基本款 Genesis 9,結果全身 25,156 個面都掛在同一個材質 Fingernails-1 上,一張貼圖都沒有。原因有兩個疊在一起:基本款 Genesis 9.duf 本來就不帶皮膚貼圖;「合併材質」選項又把幾個參數相同的預設材質併成一個。
改匯入 Laura 角色檔(裡面包含體型和皮膚材質),並把 useMergeMaterials 關掉,就拿到 7 個材質和完整的 4K 皮膚貼圖。DAZ 的 UV 是 UDIM 排法,數值會超出 0 到 1(我這份是 0 到 5),但 glTF 的貼圖預設會重複鋪,每個材質只讀到自己那一格,Godot 可以直接用。
坑 5:Genesis 9 有胸部骨頭,沒有臀部骨頭
Genesis 9 內建 l_pectoral 和 r_pectoral,還附權重(左胸有 457 個頂點受影響,最大權重 0.845),胸部物理可以直接接。臀部就沒有了,只靠骨盆和大腿骨帶動,得自己補。
我比照胸部骨頭的結構:骨頭的頭在骨盆內,尾端指向臀部最突出的點,長度約 10 cm。權重用橢球高斯從臀部中心往外衰減,最大 0.85,再從原本的骨盆和大腿權重裡按比例扣掉,讓權重總和維持不變:
r2 = (d.x / 0.075)**2 + (d.y / 0.07)**2 + (d.z / 0.085)**2 # 臀部是縱向橢圓
w = 0.85 * math.exp(-r2)
w *= smooth(center.y - 0.105, center.y - 0.045, p.y) # 只影響後側
w *= smooth(-0.005, 0.025, p.x * side) # 不越過中線
for g in v.groups:
if group_name(g) in deform_bones:
g.weight *= (1.0 - w) # 從既有權重按比例分出來
glute_group.add([v.index], w, 'REPLACE')
測試方法是把左臀骨轉 25°:左臀最大位移 31 mm,右臀和骨盆前方完全沒被帶到,表面也沒有破面。左右各 433 個頂點有權重。
順手一坑:glTF 只保留 4 根骨頭的權重
匯出 glTF 時 Blender 印了一行警告:
WARNING: There are more than 4 joint vertex influences. The 4 with highest weight will be used (and normalized).
glTF 的一組 JOINTS_0 / WEIGHTS_0 只能放 4 根骨頭,Blender 匯出器預設會直接截斷。Blender 手冊 裡的 Include All Bone Influences(Python 參數 export_all_influences=True)會改寫第二組。有開發者實測 ,他手上那個測試模型有 61.8% 的頂點被悄悄截成 4 根,而且只有 stdout 一行警告,不會報錯。臀部和胸部邊緣剛好是多骨頭交界,被截掉的往往就是新加的那根,這個選項一定要開。代價是相容性:手冊自己也提醒,超過 4 根權重的模型在不少檢視器裡可能顯示不正確。Godot 會讀取第二組 JOINTS_1 / WEIGHTS_1,這次匯入後每個頂點都是 8 組權重,後面計算組織中心的程式也是以此為前提。
Godot 端:用 SkeletonModifier3D 驅動骨頭
模型進了 Godot,物理要改成驅動骨頭。Godot 4.3 開始有 SkeletonModifier3D,依照 官方的設計文章 ,它的處理時機在 AnimationMixer 套用動畫之後,而且多個 modifier 會依照 Skeleton3D 子節點的順序執行。這正是我要的:動畫先擺好姿勢,呼吸疊上去,最後物理再疊上去。
class_name SoftBoneModifier
extends SkeletonModifier3D
func _process_modification_with_delta(delta: float) -> void:
var sk := get_skeleton()
if sk == null or delta <= 0.0:
return
for b in bones:
var world := sk.global_transform * sk.get_bone_global_pose(b.bone)
var target := world * b.local_tip
# ……彈簧積分,同第一版……
var tip := world.affine_inverse() * b.p
var q := Quaternion(b.local_tip.normalized(), tip.normalized())
sk.set_bone_pose_rotation(b.bone, sk.get_bone_pose_rotation(b.bone) * q)
_process_modification() 在新版已經標成 deprecated,文件 建議改用帶 delta 的 _process_modification_with_delta()。另外文件特別提醒,它可能在 delta 為 0 的情況下被呼叫,物理積分記得擋掉。
我原本擔心 get_bone_pose_rotation() * q 這種寫法會一幀一幀累積旋轉,所以寫了一個小測試:讓場景跑 30、120、240 幀,在 modifier 之外讀 l_pectoral 和 l_glute 相對靜止姿勢的旋轉角,三次都是 0.00°、縮放都是 1.0。在我的場景裡,modifier 的結果每幀都會被還原,不會越轉越歪。
有人會問:Godot 不是有內建 SpringBoneSimulator3D 嗎?4.4 的文件裡就有了。它的定位是頭髮、布料、尾巴這類骨鏈的慣性擺動,用起來很方便。我最後還是自己寫,主要是需求不太一樣:
| SpringBoneSimulator3D | 自製 SoftBoneModifier | |
|---|---|---|
| 適合對象 | 骨鏈(頭髮、裙擺、尾巴) | 單根末端骨(胸、臀這種團塊) |
| 參數 | stiffness、drag、gravity | 質量、剛性、阻尼,可換算成 ω₀ 和 ζ |
| 靜止下垂抵銷 | 無 | 有 |
| 沿軸伸縮(體積守恆) | 無 | 有 |
| 碰撞 | 有 SpringBoneCollision3D | 無 |
| 建議 | 骨鏈類直接用它 | 要調物理手感、做測試時用 |
另外踩到兩個 GDScript 的小坑,一起記下來。第一個是新增了 class_name 之後,用 headless 模式跑腳本出現 Identifier "GenesisBody" not declared in the current scope,原因是全域類別快取還沒更新,先跑一次 godot --headless --editor --quit 重新掃描就好。第二個是 for side in [1, -1]: 沒標型別,迴圈裡用 := 推斷型別時會出現 Cannot infer the type of "ss" variable,改成 for side: int in [1, -1]: 就解決了。
程序化步態太僵,改用動作捕捉
物理都接好後,我讓 Laura 用程序化步態走路:骨盆上下起伏和扭轉、大腿前後擺、膝蓋彎、手臂反向擺動,全部用正弦波寫的。跑起來是會走,但僵到不行,廢到笑。
問題出在真人走路的細節太多:腳跟著地到腳尖蹬地的滾動、支撐腳踩實不滑步、頭部反向穩定、手肘手腕的二次擺動,還有每個部位頻率不完全同步的那種隨機感。這些用正弦波很難湊出來,而物理疊在僵硬的動作上,看起來就更假。
所以我改用 Quaternius 的 Universal Animation Library ,CC0 授權,Standard 版在 OpenGameArt 免費下載,裡面直接附一個給 Godot 用的 glb,官方說 Standard 版有 45 個動畫,我實際打開 glb 數到 46 段(多一段 A_TPose 參考姿勢),其中有 Idle、Walk、Jog_Fwd、Sprint、Jump_Start、Jump、Jump_Land。動畫都是原地播放,沒有 root motion,剛好適合我自己控制角色沿圓形路線前進。
UAL 的骨頭是 Rigify 的 DEF- 命名,Laura 是 DAZ 命名,兩邊要對到 Godot 的標準人形骨架 SkeletonProfileHumanoid。我寫了一個腳本產生兩份 BoneMap:
var g9 := {"Hips": "hip", "Spine": "spine1", "Chest": "spine3", "UpperChest": "spine4",
"Neck": "neck1", "Head": "head", "LeftUpperArm": "l_upperarm", "LeftLowerArm": "l_forearm",
"LeftUpperLeg": "l_thigh", "LeftLowerLeg": "l_shin", "LeftFoot": "l_foot"} # 手指、右側略
var bm := BoneMap.new()
bm.profile = SkeletonProfileHumanoid.new()
for k in g9:
bm.set_skeleton_bone_name(k, g9[k])
ResourceSaver.save(bm, "res://models/retarget/g9_bonemap.tres")
接著在 .glb.import 的 _subresources 裡掛上 BoneMap:
_subresources={
"nodes": {
"PATH:Laura/Skeleton3D": {
"retarget/bone_map": Resource("res://models/retarget/g9_bonemap.tres"),
"retarget/bone_renamer/rename_bones": true,
"retarget/bone_renamer/unique_node/make_unique": true,
"retarget/bone_renamer/unique_node/skeleton_name": "GeneralSkeleton",
"retarget/rest_fixer/overwrite_axis": true,
"retarget/rest_fixer/fix_silhouette/enable": false
}
}
}
fix_silhouette 最後是 false,後面會講為什麼。重定向完,動畫軌道路徑會變成 %GeneralSkeleton:LeftUpperLeg 這種格式,只要兩邊的骨架都改名成 GeneralSkeleton,UAL 的動畫庫就能直接塞進 Laura 的 AnimationPlayer。
重定向三連坑:這段最花時間
坑 1:照文件開了「修正輪廓」,結果兩邊都扭曲
Godot 的重定向文件 對 Fix Silhouette 寫得很明確:用 SkeletonProfileHumanoid 時,T-pose 的模型不用開,A-pose 的模型應該開。Laura 是 A-pose,我就照做了,然後就是開頭那一幕:
連 T-pose 參考動畫都歪成這樣,走路當然也好不到哪去,整個人弓著身子走:
我的第一個直覺是「UAL 的動畫本來就這種風格」,畢竟右邊的人偶走路也有點低頭。直到把兩個模型並排、只看沒播動畫的原始姿勢,才發現兩邊都壞了:UAL 人偶的手臂舉到臉前方,Laura 的手臂往正前方平舉。
把 fix_silhouette 關掉,兩邊的原始姿勢立刻恢復正常,Laura 的脊椎和腿也不歪了。文件其實也有寫限制:「無法修正差異太大的輪廓,也可能無法修正骨頭的 roll」。至於為什麼會在這組模型上失效,我的推測是兩個原因疊加:DAZ 的 hip 和大腿之間還夾了一根沒對應的 pelvis,加上一堆 twist 骨;UAL 的原始姿勢裡,鎖骨方向是 (0.78, -0.08, -0.62),明顯往後掃,不是教科書式的 T-pose。這部分我沒有再往下挖,有遇過的人歡迎補充。
坑 2:A-pose 對 T-pose,手臂卡進身體
關掉修正輪廓後,身體正常了,換手臂出問題:
Laura 的手臂往後折、卡進身體,前臂歪在腰旁邊。我把兩邊原始姿勢的骨頭方向印出來比對(Godot 座標,+X 是角色左側):
| 骨段 | UAL | Laura(A-pose) |
|---|---|---|
| 上臂 → 前臂 | (1.0, 0.0, -0.02) | (0.68, -0.73, 0.02) |
| 前臂 → 手 | (1.0, 0.0, 0.02) | (0.72, -0.65, 0.24) |
| 手 → 中指 | (1.0, 0.0, 0.04) | (0.68, -0.71, 0.17) |
| 大腿 → 小腿 | (0.0, -1.0, -0.01) | (0.04, -1.0, -0.03) |
腿幾乎一樣,手臂差了約 47°。動畫存的是「相對原始姿勢轉了多少」,UAL 的手臂從水平往下轉約 80° 垂到身側,同樣的轉動套到已經斜向下的 Laura 身上,就會轉過頭。
修正輪廓本來就是要處理這件事,但它在這組模型上不可靠。我改在 Blender 裡手動處理:把 Laura 的上臂、前臂、手、四指都轉到水平,拇指對齊 UAL 的方向,再把這個姿勢烘焙成新的原始姿勢:
def align(pb, target):
bpy.context.view_layer.update()
d = (pb.tail - pb.head).normalized()
R = d.rotation_difference(target.normalized()).to_matrix().to_4x4()
h = pb.head.copy()
pb.matrix = Matrix.Translation(h) @ R @ Matrix.Translation(-h) @ pb.matrix
for s, sx in (("l", 1.0), ("r", -1.0)):
X = Vector((sx, 0, 0))
for n in ("upperarm", "forearm", "hand"):
align(rig.pose.bones[f"{s}_{n}"], X)
# 四指 index/mid/ring/pinky 1~3 節同樣對齊 X,拇指另外對齊 UAL 的方向
# 姿勢烘焙成新的原始姿勢:先套用網格的 Armature modifier,再 Apply Pose as Rest,最後重新綁回
bpy.ops.object.modifier_apply(modifier="Armature SkinBinding") # 在 mesh 上
bpy.ops.pose.armature_apply(selected=False) # 在 rig 上
body.modifiers.new("Armature SkinBinding", 'ARMATURE').object = rig
重新匯出、重新匯入後,手臂終於自然地垂在身側擺動:
坑 3:overwrite_axis 連沒對應的骨頭都改了
重定向修好了,物理卻偷偷壞掉。我的 SoftBoneModifier 一開始假設「骨頭沿本地 +Y 延伸,末端就是組織中心」,這在 Blender 匯出的骨架上成立。但重定向之後印出來一看,l_glute 的 +Y 軸方向從 (0.14, -0.31, -0.94) 變成 (0.14, 0.28, -0.95),原本往後下方,現在變成往後上方。
l_glute 根本不在 BoneMap 裡,為什麼會被改?文件對 Overwrite Axis 有一段提醒寫得很直白:這是 Godot 4 共用動畫最重要的選項,但如果模型原本的骨頭靜止姿勢很重要,它可能產生「horrible results」。它統一的是整副骨架的靜止姿勢,沒對應的骨頭也會被牽動。
我的解法是不再依賴骨頭軸向,改從蒙皮權重算出組織中心。受這根骨頭影響的頂點,用權重平方加權平均,算出骨頭本地空間裡的質心:
## Σ w²·(bind_pose · v) / Σ w²
func fit_tips_from_mesh(mi: MeshInstance3D) -> void:
var skin := mi.skin
var mesh := mi.mesh as ArrayMesh
# ……建立 bind 名稱 → 索引的對照……
for surf in mesh.get_surface_count():
var arr := mesh.surface_get_arrays(surf)
var verts: PackedVector3Array = arr[Mesh.ARRAY_VERTEX]
var bidx: PackedInt32Array = arr[Mesh.ARRAY_BONES]
var bw: PackedFloat32Array = arr[Mesh.ARRAY_WEIGHTS]
var per := bidx.size() / verts.size() # 開了 all influences 會是 8
for v in verts.size():
for k in per:
var j := bidx[v * per + k]
if sums.has(j) and bw[v * per + k] > 0.0:
var w := bw[v * per + k]
sums[j] += skin.get_bind_pose(j) * verts[v] * w * w
wsum[j] += w * w
# local_tip = sums[j] / wsum[j]
算出來的胸部質心在骨頭前方約 8 cm,臀部在後方約 9 cm,位置都合理。這招不管骨頭軸向怎麼被改都會對,換其他模型也能直接用。如果你的專案很依賴原本的骨頭靜止姿勢,文件也推薦另一條路:Realtime Retarget Module ,可以在保留原始靜止姿勢的情況下共用動畫。不過它是引擎模組,不是外掛,README 寫明要自己編譯 Godot 才能用。
全部修完的最終畫面,走路時胸部位移約 9 mm、臀部約 8 mm,右下角波形跟著步伐規律振盪:
最後一哩:量動畫速度,避免滑步
UAL 的動畫是原地踏步,角色往前移動的速度要和動畫的步幅對上,不然腳會在地上滑。我寫了一個工具,在 Laura 身上逐格播放動畫,找出支撐腳往後滑動的速度。
第一次跑出來全部是 0 m/s,乾。原因是我在 SceneTree 的 _init() 裡就開始量,節點還沒真正進入場景樹,AnimationPlayer 不會套用動畫。改成等第一幀的 _process() 再量就正常了。
第二個問題是跑步。走路時支撐期長,取「比較低那隻腳」的後退速度中位數,量到 1.05 m/s 很穩定。跑步有雙腳騰空的階段,比較低的腳不一定踩在地上;改成只取離地 1.5 cm 內的格數,Jog 又跳到 5.31 m/s,Sprint 卻只有 1.15 m/s,完全不合理。腳踝在腳跟到腳尖滾動時本身會移動,跑步貼地的格數又少,雜訊太大。
最後我老實接受估計值,而且要先講清楚,跑步這兩個數字沒有驗證過:Walk 用實測的 1.05 m/s;Jog 和 Sprint 用腳步擺幅除以「貼地比例乘上週期」推算,貼地比例分別抓 0.38 和 0.30,得到約 3.3 和 5.5 m/s。程式依目前速度選原生速度最接近的動畫(用對數比例比較),再用播放速率補差。速度調高時腳可能還會有點滑,這部分之後想用腳部 IK 處理,Godot 4.6 已經加回 TwoBoneIK3D 等 IK modifier 。
Troubleshooting 速查
症狀 → 解法
- Diffeomorphic 從 Bitbucket 下載回 402:Bitbucket 免費方案停止檔案下載,改從 GitHub 下載。
- easy_import_daz 回 FINISHED 但場景是空的:傳
directory加files,不要用filepath;再用temp_override補上 VIEW_3D 的 context。- 整個角色只剩一個材質、沒有貼圖:基本款 Genesis 9 本來就沒有貼圖,改匯入角色檔,並關掉
useMergeMaterials。- glTF 匯出警告超過 4 根骨頭:開
export_all_influences=True,Godot 會讀到 8 根。- 重定向後整個人駝背、手往前伸:先把兩邊的原始姿勢並排對照。在我這組 DAZ 加 UAL 的模型上,開和關
fix_silhouette各試過一次,關掉才正常;換別的模型請自己對照,不要直接套用。- 手臂卡進身體:原始姿勢 A/T 不一致,在 Blender 把手臂擺成和動畫來源一樣的方向,再 Apply Pose as Rest。
- 重定向後物理目標點跑掉:overwrite_axis 會改到未對應的骨頭,改用蒙皮權重計算組織中心。
- headless 跑腳本找不到新的 class_name:先
godot --headless --editor --quit重建類別快取。- 動畫量測工具讀到的骨頭都沒動:等第一幀
_process()之後再量。
寫在最後
回頭看,這次最大的教訓不是哪個選項要開、哪個要關,而是除錯時要把兩邊並排、從原始姿勢開始比。我在「動畫風格就是這樣」這個假設上卡了好一陣子,直到把 UAL 人偶和 Laura 放在一起、關掉動畫,問題才一眼看穿。重定向牽涉兩副骨架、兩組靜止姿勢、一組標準骨架,任何一邊歪掉,症狀都會出現在另一邊。
另一個體會是,物理疊在動作上的真實感,有八成取決於動作本身。彈簧參數我調了半天,換成動作捕捉之後,同一組參數突然就順眼了。我這肝也算沒白爆。
如果你也在 Godot 做角色動畫或二次運動,特別是要把 DAZ、VRoid 這類外部模型接上共用動畫,希望這份紀錄能幫你省下幾個小時。有遇過 fix silhouette 在其他模型上也失效的,或是有更好的跑步速度量法,歡迎留言交流。
參考資料
- Retargeting 3D Skeletons — Godot Docs
- Design of the Skeleton Modifier 3D — Godot Engine
- SkeletonModifier3D — Godot Docs
- SpringBoneSimulator3D — Godot Docs (4.4)
- Inverse Kinematics Returns to Godot 4.6 — Godot Engine
- Realtime Retarget Module — TokageItLab
- Diffeomorphic DAZ Importer — GitHub
- Diffeomorphic 官方部落格
- Diffeomorphic import_daz version_5_2_0 tag — GitHub
- Daz 3D Interactive Licenses
- glTF 2.0 — Blender 4.2 LTS Manual
- No Error When Bone Weights Change on glTF Export from Blender — Zenn
- Integration Basics — Gaffer On Games
- Universal Animation Library — Quaternius
- Universal Animation Library — OpenGameArt
沒有留言:
張貼留言