LEVEL 4 · ระดับเทพ
หนึ่งเฟรมถูกคำนวณยังไง
ทั้งเล่มปลูกคำถามไว้หลายข้อ บทนี้ไขพร้อมกันทั้งหมดด้วยคำตอบเดียว
ทุกครั้งที่เฟรมเปลี่ยน Blender ไม่ได้ "อัปเดตของที่เปลี่ยน" แบบสุ่ม มันมีแผนผังชื่อ dependency graph (depsgraph) ที่บอกว่าอะไรต้องคำนวณก่อนอะไร แล้วเดินตามแผนนั้นทุกเฟรม
F-Curve ที่เขียนลง property จริง ที่เหลือทำงานบนผลลัพธ์13.1 ดูทั้งสายพร้อมกัน
สร้างกล่องหนึ่งใบที่มีครบสามชั้นทับกันอยู่:
- keyframe บน
location.x— วิ่งจาก 0 ไป 2 ระหว่างเฟรม 1 ถึง 41 - driver บน
scale.z— อ่านจากตัวควบคุม ไม่เกี่ยวกับเวลา - constraint Limit Location — ห้าม x เกิน 1.5
แล้วไล่ดูทีละเฟรม:
frame | F-Curve ให้ x | property x | x จริงหลังประเมิน | scale.z
--------------------------------------------------------------------------
1 | 0.0000 | 0.0000 | 0.0000 | 1.750
11 | 0.3125 | 0.3125 | 0.3125 | 1.750
21 | 1.0000 | 1.0000 | 1.0000 | 1.750
31 | 1.6875 | 1.6875 | 1.5000 | 1.750
41 | 2.0000 | 2.0000 | 1.5000 | 1.750อ่านตารางนี้ทีละคอลัมน์ แล้วทั้งเล่มจะประกอบร่างพร้อมกัน:
- คอลัมน์ 2 กับ 3 ตรงกันเสมอ — F-Curve เขียนลง property จริง นี่คือชั้นเดียวที่มีสิทธิ์แตะค่าที่คุณเก็บไว้
- คอลัมน์ 4 แยกออกจากคอลัมน์ 3 ตั้งแต่เฟรม 31 — พอค่าเกิน 1.5 constraint เข้ามาตัด ค่าใน property ยังเป็น 1.6875 และ 2.0000 ครบถ้วน แต่ผลจริงถูกล็อกไว้ที่ 1.5
- คอลัมน์ 5 ไม่เปลี่ยนเลยตลอด 41 เฟรม — เพราะ driver อ่านจากตัวควบคุมที่ไม่ได้ขยับ เวลาผ่านไปเท่าไหร่ก็ไม่เกี่ยว
13.2 ลำดับมาจากไหน — และทำไมมันไม่ใช่ท่อคงที่
ตรงนี้คือจุดที่คนอธิบายผิดกันบ่อยที่สุด รวมถึงคำอธิบายที่หาเจอทั่วไปบนอินเทอร์เน็ต
ความเข้าใจผิดคือคิดว่า Blender มี ท่อคงที่ — ทำอย่างนี้ก่อน แล้วอย่างนั้น เรียงเหมือนกันทุกครั้ง ถ้าเป็นแบบนั้นจริง คุณจะท่องลำดับได้ แต่คุณจะทำนายผิดทันทีที่เจอไฟล์ที่ต่อของกันคนละแบบ
ของจริง: depsgraph สร้างลำดับขึ้นใหม่จากคำถามเดียว — ใครอ่านค่าจากใคร
ไม่มีใครได้คิวเพราะเป็นชนิดนั้น ทุกคนได้คิวเพราะมีคนรอค่าของตัวเองอยู่
ลองพิสูจน์ด้วยตาเอง สามการทดลอง:
หนึ่ง — ให้ driver อ่านค่าที่ถูกคีย์
frame 1: F-Curve ให้ Ctrl.x = 0.0000 -> driver ได้ scale.z = 1.0000
frame 13: F-Curve ให้ Ctrl.x = 5.0000 -> driver ได้ scale.z = 6.0000
frame 25: F-Curve ให้ Ctrl.x = 10.0000 -> driver ได้ scale.z = 11.0000driver เห็นค่าของเฟรมปัจจุบัน ไม่ใช่ค่าเฟรมก่อน ⇒ มันถูกประเมินหลัง F-Curve ที่ป้อนมัน เพราะมันรออยู่
สอง — ให้ constraint เล็งเป้าที่ถูก driver ขับ
Target.x หลังประเมิน = 7.0000 ← มาจาก driver ล้วน ไม่มี keyframe เลย
Follower.x หลังประเมิน = 7.0000 ← constraint เห็นผลของ driver แล้ว⇒ constraint ตัวนี้ถูกประเมินหลัง driver ตัวนั้น ด้วยเหตุผลเดียวกันเป๊ะ
สาม — constraint สองตัวเดิม สลับลำดับใน stack
Limit(max 2) แล้วค่อย CopyLocation(9) -> x = 9.0000
CopyLocation(9) แล้วค่อย Limit(max 2) -> x = 2.0000ของสองชิ้นเดิม ค่าเดิม ได้คนละคำตอบ เพราะตัวล่างรับผลของตัวบนมาทำต่อ
13.3 กฎที่เชื่อได้ แทนลำดับที่ท่องไว้
จากสามการทดลองข้างบน สิ่งที่เชื่อได้จริงมีสามข้อ ไม่ใช่ลำดับ 1-2-3-4:
- มีที่เดียวที่เขียนลง property จริง คือ F-Curve — คอลัมน์ 2 กับ 3 ในตารางบทนี้ตรงกันทุกแถวเพราะเหตุนี้ ชั้นอื่นทั้งหมดทำงานบนผลลัพธ์ ไม่ใช่บนค่าที่คุณเก็บ
- ใครอ่านค่าจากใคร คนนั้นมาทีหลัง — ครอบทั้ง driver, constraint, พ่อ-ลูก และ modifier โดยไม่ต้องจำแยก
- ในกองเดียวกัน ลำดับบนลงล่างคือลำดับจริง — constraint stack และ modifier stack เรียงได้ เพราะลำดับมีความหมาย
ค่าที่คุณเก็บ กับค่าที่คุณเห็น เป็นคนละอย่าง และมีชั้นคั่นระหว่างมันเสมอ
💡 ที่พูดว่า "ไม่ใช่ท่อคงที่" ไม่ได้แปลว่ามันสุ่ม — ไฟล์ไหนที่ต่อของแบบเดียวกัน ก็ได้ลำดับเดิมทุกเฟรมแน่นอน สิ่งที่เปลี่ยนคือลำดับเกิดจากวิธีที่คุณต่อของ ไม่ได้ถูกกำหนดมาก่อน
13.4 ปริศนาทั้งเล่ม ไขพร้อมกัน
ทุกข้อใช้กฎข้อ 1 กับข้อ 2 ข้างบนตอบได้หมด:
- ทำไมคีย์ไว้ (0,0,0) แล้วของไปอยู่ (7,3,2)? — constraint อ่านผลลัพธ์ของ property นั้นเพื่อทำงาน มันจึงมาทีหลัง (กฎ 2) และมันไม่มีสิทธิ์เขียนกลับ (กฎ 1) ค่าที่คุณคีย์จึงอยู่ครบ
- ทำไม IK แก้มุมได้ 78.4 องศา แต่ค่าใน property ยัง 0? — IK คือ constraint ตัวหนึ่ง กฎ 1 ห้ามมันเขียนกลับเหมือนกันทุกตัว ไม่มีข้อยกเว้น
- ทำไมหมุนพ่อแล้วลูกย้ายที่ทั้งที่ค่าลูกเป็นศูนย์? — ลูกอ่านเมทริกซ์ของพ่อ (กฎ 2) พ่อจึงเสร็จก่อนเสมอ และค่าที่เก็บของลูกไม่ต้องเปลี่ยนเลย
- ทำไม weight ที่ดูถูกแล้วเนื้อยังบิด? — Armature modifier อ่านตำแหน่งกระดูกหลัง constraint ทำงานแล้ว ถ้ากระดูกไปผิดที่ก่อนหน้านั้น เนื้อก็บิดตามอย่างซื่อสัตย์ ปัญหาไม่ได้อยู่ที่ weight
- ทำไม driver ที่อ้างวนกันถึงพัง? — นี่คือคำตอบที่ชัดที่สุดว่าทำไมมันเป็นกราฟไม่ใช่ท่อ: ถ้า A รอ B และ B รอ A ก็ไม่มีลำดับไหนที่ถูก ท่อคงที่จะไม่มีปัญหานี้เลย เพราะมันไม่เคยถามว่าใครรอใคร
13.5 วิธีไล่ปัญหาที่ใช้ได้จริงตั้งแต่วันนี้
เมื่อ "ของไม่เป็นอย่างที่คิด" ให้ไล่ถอยจากปลายทางกลับมาต้นทาง เพราะคนที่ได้คิวหลังคือคนที่ชนะ:
| # | ดูอะไร | เปิดดูที่ไหน |
|---|---|---|
| 1 | มี modifier อยู่ไหม (Armature, Lattice) | แผงขวา แท็บรูปประแจ |
| 2 | มี constraint อยู่ไหม และเรียงยังไง | แผงขวา แท็บรูปโซ่ (กระดูกใช้แท็บโซ่ที่มีรูปกระดูก) |
| 3 | วัตถุนี้มีพ่อหรือเปล่า | แผงขวา แท็บสี่เหลี่ยมส้ม › Relations › Parent |
| 4 | ค่าใน property คืออะไร ตรงกับที่คีย์ไหม | กด N ในวิวพอร์ต แถบด้านขวาจะเด้งออกมา |
| 5 | มี driver อยู่บนช่องนั้นไหม | ช่องที่มี driver จะเป็นสีม่วง — คีย์เฟรมเป็นสีเหลือง/เขียว |
💡 จำสีให้แม่นแล้วประหยัดเวลาได้มาก: เขียว = มีคีย์เฟรมแต่ไม่ใช่เฟรมนี้ · เหลือง = มีคีย์ตรงเฟรมนี้พอดี · ม่วง = ถูก driver ขับอยู่ ไม่ใช่คีย์เฟรม สีบอกให้รู้ตั้งแต่ยังไม่ต้องคลิกว่าใครเป็นเจ้าของค่านั้น
เคล็ดลับ: ถ้าเปิดดูแล้วค่าในช่องถูกต้อง แต่ของไปอยู่ผิดที่ แปลว่าปัญหาไม่ได้อยู่ที่ข้อ 4–5 เลย ให้ไปดูข้อ 1–3 ทันที — ค่าถูกแต่ผลผิด คือนิยามของ "มีชั้นอื่นทำงานทับอยู่" ไม่ต้องเสียเวลาไล่ keyframe