คู่มือ Blender Animation
เข้าใจว่าค่าที่เห็นบนจอมาจากไหน — keyframe, armature, constraint, driver จนถึงลำดับที่ Blender คำนวณหนึ่งเฟรม อ่านจบแล้วของไม่ขยับตามที่คีย์ก็ไล่เองเป็น
คู่มือที่พาคุณจากคนที่เคยเปิด Blender แล้วปิดหนี ไปจนถึงจุดที่มองหน้าจอแล้วบอกได้ว่า ค่าที่เห็นตรงหน้ามาจากไหน — ของไม่ขยับตามที่คีย์ไว้ คุณจะไล่เองเป็นว่าชั้นไหนเขียนทับ ไม่ใช่ลองสุ่มกดจนกว่ามันจะถูก
- ระดับ: เริ่มจากศูนย์ → ระดับเทพ
- เวลาอ่าน: ~75 นาที
- ภาษา: ไทย พร้อมไดอะแกรมและผลรันจริงจาก Blender
ทุกผลลัพธ์ในเล่มนี้มาจากการรัน Blender 5.2.1 LTS จริงในโหมด background ไม่มีตัวเลขไหนพิมพ์เอง คุณรันซ้ำเองได้ทุกอัน
LEVEL 0 · ปูพื้นจากศูนย์
แอนิเมชันไม่ใช่การขยับ แต่คือค่าที่เปลี่ยน#
คนส่วนใหญ่เลิกเล่น Blender ตรงจุดเดียวกัน: กดตามคลิปได้จนจบ แต่พอของไม่ขยับตามที่คิด ก็ไม่รู้จะเริ่มไล่จากตรงไหน เพราะสิ่งที่เรียนมาคือ ลำดับการกดปุ่ม ไม่ใช่ สิ่งที่เกิดขึ้นข้างใน
ความเข้าใจผิดที่พบบ่อยที่สุดคือคิดว่าแอนิเมชันคือ "การสั่งให้ของขยับ" — เหมือนผลักรถเข็น คุณออกแรง มันเคลื่อน ถ้าคิดแบบนี้ พอมันไม่เคลื่อนคุณจะหาว่า "แรงหายไปไหน" แต่คำถามนี้ไม่มีคำตอบใน Blender เพราะไม่เคยมีแรงอยู่ตั้งแต่แรก
ของจริงตรงข้ามเลย Blender ไม่เคยขยับอะไรทั้งนั้น มันแค่ คำนวณค่าชุดหนึ่งใหม่ทุกเฟรม แล้ววาดตามค่านั้น
ทุกอย่างที่ขยับได้ คือค่าของ property ตัวหนึ่ง ที่ถูกคำนวณใหม่ทุกเฟรม
ประโยคนี้คือแกนของทั้งเล่ม ทุกบทหลังจากนี้คือการตอบคำถามเดียว: ใครเป็นคนคำนวณค่านั้น และคำนวณตามลำดับไหน
- keyframe คำนวณจากเส้นโค้ง — บทที่ 3, 4
- กระดูกคำนวณจากพ่อของมัน — บทที่ 7
- constraint คำนวณทับผลลัพธ์ที่ได้มาแล้ว — บทที่ 10
- driver คำนวณจาก property ตัวอื่น — บทที่ 11
💡 property คือช่องเก็บค่าหนึ่งช่องของวัตถุ เช่นตำแหน่งแกน X, ขนาดแกน Z, ความทึบของวัสดุ ทุกช่องที่คุณเห็นในแผงด้านขวาของ Blender คือ property หนึ่งตัว และเกือบทุกตัวแอนิเมตได้
1.1 ทำไมการแยกสองอย่างนี้ถึงสำคัญ
เพราะมันเปลี่ยนคำถามที่คุณถามเวลาแก้ปัญหา
คนที่คิดว่า "ของขยับ" จะถามว่า "ทำไมมันไม่ขยับ" คำถามนี้ไม่มีที่ให้ไล่ต่อ ส่วนคนที่รู้ว่า "ค่าถูกคำนวณ" จะถามว่า "ค่านี้ถูกคำนวณโดยใคร และมีใครมาเขียนทับทีหลังหรือเปล่า" — คำถามหลังนี้ตอบได้เสมอ และเล่มนี้จะสอนวิธีไล่จนถึงตัวจริงทุกครั้ง
ปริศนาข้อแรกที่จะติดค้างไปจนถึงบทที่ 10: คุณคีย์ค่าไว้ค่าหนึ่ง แล้วของไปอยู่อีกที่ ทั้งที่ค่าที่คุณคีย์ยังอยู่ครบไม่ถูกแตะเลย เป็นไปได้ยังไง
Data path — ชื่อจริงของสิ่งที่ขยับได้#
ถ้าทุกอย่างคือ property แล้ว Blender เรียกชื่อ property แต่ละตัวว่ายังไง
คำตอบคือ data path — สตริงหนึ่งเส้นที่บอกทางไปหาช่องเก็บค่านั้น เหมือนที่อยู่บ้าน ไม่ใช่ชื่อเล่น
ลองคีย์ตำแหน่งกล่องหนึ่งใบสองจุด แล้วเปิดดูว่า Blender เก็บอะไรไว้จริง ๆ:
cube.location.x = 0.0
cube.keyframe_insert(data_path="location", index=0, frame=1)
cube.location.x = 10.0
cube.keyframe_insert(data_path="location", index=0, frame=25)ผลที่ได้:
action name : BoxAction
จำนวน F-Curve : 1
data_path : 'location' | array_index: 0 ← ชื่อจริงของสิ่งที่ถูกคีย์
จำนวน keyframe : 2สังเกตสองอย่าง: ชื่อคือ location ไม่ใช่ location.x และมี array_index: 0 แยกออกมาต่างหาก
2.1 ทำไมต้องแยก path กับ index
เพราะ location เป็นค่าสามช่องรวมกัน (x, y, z) Blender จึงเก็บ path ไว้เส้นเดียวแล้วใช้ตัวเลข index ชี้ว่าเป็นช่องไหน — 0 คือ X, 1 คือ Y, 2 คือ Z
นี่คือเหตุผลเชิงออกแบบ ไม่ใช่ความบังเอิญ: ถ้าคีย์ทั้งสามแกน คุณจะได้เส้นโค้งสามเส้นที่ใช้ path เดียวกันแต่ index ต่างกัน ทำให้ Blender จัดกลุ่มมันเข้าด้วยกันในหน้าจอได้เอง โดยไม่ต้องมีตารางแยกบอกว่าเส้นไหนเป็นพวกเดียวกัน
💡 data path เขียนตามโครงสร้างข้อมูลจริงของ Blender เช่น location (ตำแหน่งวัตถุ) · rotation_euler (การหมุน) · scale (ขนาด) · ยิ่งของอยู่ลึก path ยิ่งยาว แต่หลักการเดิม คือทางเดินจากวัตถุไปถึงช่องเก็บค่า:
location ← ตำแหน่งของวัตถุ
pose.bones["forearm"].rotation_quaternion ← การหมุนของกระดูกชื่อ forearm2.2 ทำไมคุณต้องรู้เรื่องนี้ตั้งแต่บทที่ 2
เพราะ data path เป็นสิ่งที่โผล่ซ้ำในทุกกลไกที่เหลือของเล่ม
- ตอนคีย์ — มันคือสิ่งที่บอกว่าคีย์อะไร (บทที่ 3)
- ตอนตั้ง driver — มันคือปลายทางที่ค่าจะไปลง (บทที่ 11)
- ตอนไล่หาว่าใครเขียนทับ — มันคือชื่อที่คุณใช้ค้น (บทที่ 13)
พูดอีกแบบ: ถ้าตอบไม่ได้ว่ากำลังจะแอนิเมต data path ไหน แปลว่ายังไม่รู้ว่ากำลังจะทำอะไร
LEVEL 1 · พื้นฐานที่ใช้ทุกวัน
Keyframe คือจุดบนเส้นโค้ง ไม่ใช่ท่าที่จำไว้#
ความเข้าใจผิดที่แพงที่สุดในเล่มนี้: คิดว่า keyframe คือ "ท่า" ที่ Blender ถ่ายรูปเก็บไว้ แล้วค่อยเล่นสลับให้ดู
ถ้าเป็นแบบนั้นจริง เฟรมที่อยู่ระหว่างสองคีย์ก็ควรจะว่างเปล่า แต่มันไม่ว่าง ลองดูของจริงจากไฟล์เดียวกับบทที่แล้ว — คีย์แค่สองจุด คือเฟรม 1 อยู่ที่ 0 และเฟรม 25 อยู่ที่ 10:
จุด (frame, value) = (1.0, 0.0) | interpolation: BEZIER
จุด (frame, value) = (25.0, 10.0) | interpolation: BEZIERแล้วถามค่าที่เฟรมที่ ไม่ได้คีย์ไว้เลย:
frame 1 -> location.x = 0.0000
frame 5 -> location.x = 0.7407 ← ไม่มีใครคีย์เฟรมนี้ แต่มีค่า
frame 7 -> location.x = 1.5625
frame 13 -> location.x = 5.0000 ← กึ่งกลางเวลาพอดี ได้กึ่งกลางระยะพอดี
frame 19 -> location.x = 8.4375
frame 25 -> location.x = 10.0000มีค่าทุกเฟรม ทั้งที่คีย์ไว้แค่สองจุด
keyframe ไม่ใช่ท่าที่ถูกจำ แต่คือจุดที่คุณปักลงบนเส้นโค้ง — ส่วนที่เหลือของเส้น Blender คำนวณให้
fcurve.evaluate() ของจริง3.1 สิ่งที่เกิดขึ้นจริงตอนคุณกด I
กด I หนึ่งครั้ง Blender ทำสามอย่างเรียงกัน:
- อ่านค่าปัจจุบันของ property นั้น
- หา F-Curve ที่ผูกกับ data path นั้น ถ้ายังไม่มีก็สร้างใหม่
- ปักจุดหนึ่งจุดลงบนเส้นนั้น ที่พิกัด (เฟรมปัจจุบัน, ค่าที่อ่านได้)
💡 F-Curve คือ "เส้นโค้งฟังก์ชัน" — กราฟที่รับเลขเฟรมเข้าไป แล้วคืนค่าของ property ออกมาหนึ่งค่า ทุก property ที่ถูกคีย์จะมี F-Curve ของตัวเองหนึ่งเส้นเสมอ
3.2 ทำไมความเข้าใจนี้ถึงเปลี่ยนวิธีทำงาน
เพราะถ้า keyframe เป็นแค่จุดบนเส้น สิ่งที่คุณควบคุมได้ก็มีมากกว่าที่คิด — คุณไม่ได้ควบคุมแค่ "ตอนไหนอยู่ตรงไหน" แต่ควบคุม รูปทรงของเส้นระหว่างจุด ได้ด้วย และรูปทรงนี้เองที่แยกงานที่ดูเป็นหุ่นยนต์ ออกจากงานที่ดูมีน้ำหนัก
และนั่นคือบทถัดไป
F-Curve กับ interpolation — รูปทรงของเวลา#
กลับไปดูตัวเลขในบทที่แล้วอีกครั้ง แล้วคิดเลขตาม
เฟรม 5 ผ่านไป 4 เฟรมจากทั้งหมด 24 เฟรม = 17% ของเวลา แต่ค่าที่ได้คือ 0.7407 จากระยะทั้งหมด 10 = 7.4% ของระยะ
เดินทางไป 17% ของเวลา แต่ได้ระยะแค่ 7.4% แปลว่ามันออกตัวช้า
ทีนี้ดูเฟรม 13 ที่เป็นกึ่งกลางเวลาพอดี ได้ค่า 5.0000 = กึ่งกลางระยะพอดีเป๊ะ และเฟรม 19 (75% ของเวลา) ได้ 8.4375 = 84% ของระยะ คือช่วงกลางมันวิ่งเร็วกว่าค่าเฉลี่ย
นี่คือ ease-in / ease-out ที่มองเห็นเป็นตัวเลข — ออกตัวช้า เร่งกลางทาง ชะลอตอนจบ Blender ให้มาเป็นค่าเริ่มต้นโดยที่คุณไม่ต้องทำอะไร เพราะ interpolation ของทุกคีย์ตั้งต้นเป็น BEZIER
BEZIER เป็นค่าเริ่มต้นเพราะของจริงไม่มีอะไรออกตัวเต็มความเร็วทันที4.1 สามทรงที่ต้องแยกออก
| ทรง | เส้นระหว่างจุดเป็นยังไง | ใช้ตอนไหน |
|---|---|---|
CONSTANT | ค้างค่าเดิมไว้ แล้วกระโดดทันทีที่ถึงคีย์ถัดไป | สลับภาพ, เปิด/ปิด, แอนิเมชันแบบกระตุก |
LINEAR | เส้นตรง ความเร็วคงที่ตลอด | ของหมุนสม่ำเสมอ, สายพาน, เข็มนาฬิกา |
BEZIER | โค้ง มีหัวมีหางปรับได้ | ค่าเริ่มต้น และเกือบทุกอย่างที่ต้องดูมีน้ำหนัก |
ตารางข้างบนเป็นคำบรรยาย ทีนี้ดูเป็นตัวเลข — คีย์สองจุดเหมือนกันเป๊ะทั้งสามช่อง เปลี่ยนแค่ interpolation:
frame | CONSTANT | LINEAR | BEZIER
----------------------------------------------
1 | 0.0000 | 0.0000 | 0.0000
7 | 0.0000 | 2.5000 | 1.5625
13 | 0.0000 | 5.0000 | 5.0000
19 | 0.0000 | 7.5000 | 8.4375
25 | 10.0000 | 10.0000 | 10.0000อ่านทีละคอลัมน์แล้วเห็นนิยามของมันเลย:
CONSTANTค้างที่ 0 ทุกเฟรม แล้วกระโดดเป็น 10 ตอนจบLINEARได้ 2.5 แล้ว 5.0 แล้ว 7.5 — ระยะห่างเท่ากันทุกช่วง นั่นคือความเร็วคงที่BEZIERออกตัวช้ากว่า (ได้ 1.5625 ตอนที่LINEARไปถึง 2.5 แล้ว) ไล่ทันพอดีตรงกลางที่ 5.0 แล้วชะลอตอนท้าย
4.2 คำถามออกแบบ — ทำไมค่าเริ่มต้นถึงเป็น Bezier
เพราะของจริงในโลกไม่มีอะไรออกตัวจากศูนย์ไปความเร็วเต็มในทันที มวลทุกก้อนต้องใช้เวลาเร่งและเวลาเบรก ถ้าค่าเริ่มต้นเป็น LINEAR งานทุกชิ้นที่มือใหม่ทำจะดูเหมือนหุ่นยนต์ทันที Blender จึงเลือกตั้งค่าที่ "ถูกโดยปริยาย" ให้ก่อน
💡 อุปมาที่ใช้ได้: ทรงของเส้นโค้ง ≈ การเหยียบคันเร่งกับเบรกของรถ ชันมาก = เร่งแรง แบน = จอดนิ่ง — แต่จุดที่อุปมานี้ใช้ไม่ได้ คือ F-Curve ไม่ได้แทน "ความเร็ว" มันแทน ค่า ความเร็วคือความชันของมันต่างหาก เส้นที่แบนไม่ได้แปลว่าช้า แต่แปลว่า ไม่เปลี่ยนค่าเลย
4.3 เคล็ดลับที่ใช้ได้ทันที
ถ้างานดูแข็ง ให้เปิด Graph Editor แล้วดูรูปเส้นก่อนไปยุ่งกับตำแหน่งคีย์ ปัญหา "ดูเป็นหุ่นยนต์" เก้าในสิบครั้งอยู่ที่ รูปทรงระหว่างคีย์ ไม่ใช่ที่ตัวคีย์
Dope Sheet กับ Graph Editor — ข้อมูลชุดเดียว สองมุมมอง#
มือใหม่มักคิดว่าสองหน้าต่างนี้เป็นเครื่องมือคนละตัว ทำคนละงาน ความจริงคือมันมองข้อมูลชุดเดียวกัน — F-Curve ชุดเดิมที่บทที่แล้วพูดถึง — แค่ทิ้งข้อมูลคนละส่วนออกไป
| Dope Sheet | Graph Editor | |
|---|---|---|
| แสดง | เฉพาะ เวลา ของแต่ละคีย์ | ทั้งเวลาและค่า |
| แกนตั้งคือ | รายการช่อง (คนละ property คนละแถว) | ค่าของ property |
| เหมาะกับ | จัดจังหวะ ขยับคีย์ไปมา เทียบหลายช่องพร้อมกัน | ปรับรูปทรง ปรับความเร็ว แก้ค่าที่เกิน |
5.1 ทำไมต้องมีสองอัน
เพราะสองงานนี้ต้องการให้ทิ้งข้อมูลคนละอย่าง
ตอนจัดจังหวะ คุณอยากเห็นว่าคีย์ของแขน ขา และหัวเรียงกันยังไงในเวลา — ถ้าเอาแกนค่ามาแสดงด้วย ทุกช่องจะทับกันจนอ่านไม่ออก Dope Sheet จึงบีบทุกช่องให้เหลือแถวละเส้น แลกกับการมองไม่เห็นค่า
ตอนแก้ความรู้สึกของการเคลื่อนไหว คุณต้องเห็นรูปเส้น แต่ Dope Sheet ให้ไม่ได้เลย
💡 อุปมา: Dope Sheet เหมือนตารางเวลารถไฟ (ขบวนไหนออกกี่โมง) · Graph Editor เหมือนกราฟความเร็วของขบวนเดียว (เร่งตรงไหน เบรกตรงไหน) — คนละคำถาม ข้อมูลชุดเดียวกัน
5.2 ลองพิสูจน์ด้วยตาเอง
วิธีเปิดสองหน้าต่างพร้อมกันแบบเร็วที่สุด: กดแท็บ Animation ที่แถบบนสุดของ Blender — มันจัดหน้าจอให้เสร็จมาแล้ว มี Dope Sheet รออยู่ด้านล่าง
ถ้าอยากสลับหน้าต่างไหนเป็นอะไรเอง ให้กดที่ไอคอนมุมซ้ายบนของหน้าต่างนั้น แล้วเลือก editor ที่ต้องการจากรายการ — ทุกหน้าต่างใน Blender เปลี่ยนเป็นอะไรก็ได้ ไม่มีหน้าต่างไหนถูกล็อกไว้กับงานเดียว
พอเปิดได้แล้ว ลากคีย์ใน Dope Sheet ไปทางขวา จะเห็นจุดใน Graph Editor ขยับตามทันที เพราะมันคือจุดเดียวกัน ไม่ใช่สำเนา
Action — กล่องเก็บเส้นโค้ง และทำไมต้องมี#
ย้อนกลับไปดูผลรันในบทที่ 2 อีกครั้ง มีบรรทัดหนึ่งที่ยังไม่ได้อธิบาย:
action name : BoxActionไม่มีใครสั่งให้สร้าง "BoxAction" เลย มันโผล่มาเองตอนกดคีย์ครั้งแรก
Action คือกล่องที่รวม F-Curve หลายเส้นไว้ด้วยกันเป็นชุดเดียว กล่องนี้ผูกกับวัตถุ แต่แยกออกจากวัตถุได้ ซึ่งเป็นจุดสำคัญทั้งหมด
6.1 คำถามออกแบบ — ทำไมต้องมีชั้นนี้
ถ้า F-Curve ผูกติดกับวัตถุตรง ๆ โดยไม่มีกล่องคั่น สิ่งเหล่านี้จะทำไม่ได้เลย:
- ใช้ท่าเดินซ้ำกับตัวละครหลายตัว — เพราะเส้นโค้งจะติดอยู่กับตัวแรกตลอดไป
- สลับท่าไปมา เช่น เดิน / วิ่ง / กระโดด แล้วเลือกว่าตอนนี้ใช้อันไหน
- ผสมสองท่าเข้าด้วยกัน เช่น เดินอยู่แล้วค่อย ๆ กลายเป็นวิ่ง
การมีกล่องทำให้ "ชุดของการเคลื่อนไหว" กลายเป็นของที่หยิบยกส่งต่อได้ แทนที่จะเป็นสมบัติติดตัววัตถุ
💡 Action ≈ ไฟล์เอกสารหนึ่งไฟล์ · วัตถุ ≈ โปรแกรมที่เปิดไฟล์นั้นอยู่ — เปิดไฟล์อื่นก็ได้ ให้โปรแกรมอื่นเปิดไฟล์เดียวกันก็ได้
6.2 โครงข้างในของ Action ใน Blender รุ่นใหม่
ตั้งแต่ Blender 4.4 เป็นต้นมา ข้างใน Action มีชั้นเพิ่มขึ้น: layers → strips → channelbags → แล้วถึงจะเจอ fcurves
นี่คือเหตุผลที่โค้ดตัวอย่างในเล่มนี้ต้องไล่ลงไปหลายชั้นกว่าจะถึงเส้นโค้ง แทนที่จะเรียก action.fcurves ตรง ๆ แบบรุ่นเก่า (ทางเรียกแบบเก่ายังใช้ได้ถึง 4.x แต่ถูกถอดออกจริงใน Blender 5.0):
for layer in action.layers:
for strip in layer.strips:
for cb in strip.channelbags:
fcurves.extend(cb.fcurves)ชั้นที่เพิ่มมาไม่ได้ทำหน้าที่เดียวกันทั้งหมด แยกให้ชัดจะได้ไม่สับสน:
- Slot คือตัวที่ทำให้ Action หนึ่งกล่องเก็บงานของ วัตถุหลายตัว ได้พร้อมกัน ซึ่งรุ่นเก่าทำไม่ได้ — นี่คือของใหม่ตัวจริงของ 4.4
- Layer กับ Strip คือโครงสำหรับ ซ้อนชั้นเพื่อผสมกัน ในอนาคต ตอนนี้ Blender ยังล็อกไว้ที่ 1 layer และ 1 strip ต่อ Action เพราะฟีเจอร์ยังไม่เสร็จ
- Channelbag คือกล่องที่ถือ F-Curve จริง ๆ — พูดง่าย ๆ คือ Action แบบเดิมทั้งก้อน ถูกยัดลงมาอยู่ตรงนี้
⚠️ อย่าเสียเวลาลองสร้าง layer ตัวที่สอง มันยังทำไม่ได้ในตอนนี้ และไม่ใช่เพราะคุณทำผิด
6.3 NLA — ชั้นที่อยู่เหนือ Action
เมื่อ Action กลายเป็นก้อนที่หยิบยกได้แล้ว ขั้นต่อไปคือเอาหลายก้อนมาวางเรียงและผสมกันบนแทร็ก เหมือนตัดต่อวิดีโอ นั่นคือ NLA (Non-Linear Animation) เล่มนี้แตะแค่ให้รู้จักในบทที่ 14 เพราะมันเป็นเรื่องของการ ประกอบ ไม่ใช่กลไกของ ค่า
LEVEL 2 · ระดับกลาง
Armature — ทำไมกระดูกเป็นแค่ระบบพิกัด#
คำว่า "กระดูก" หลอกให้คิดว่ามันเป็นของแข็งที่ดันเนื้อให้ขยับ ถ้าคิดแบบนั้น พอเจอกรณีที่เนื้อบิดผิดรูปคุณจะหาไม่เจอว่าผิดตรงไหน เพราะกระดูกก็ดู "ถูก" ไปหมด
ของจริง: กระดูกไม่ได้ดันอะไรเลย มันคือระบบพิกัดที่มีพ่อมีลูก และการหมุนพ่อ ทำให้ทุกอย่างที่วัดจากพ่อเปลี่ยนตาม โดยที่ค่าของลูกไม่ถูกแตะ
upper_arm · forearm ไม่ถูกแตะเลย ค่าหมุนของมันยัง (0, 0, 0) แต่หัวกระดูกย้ายจาก (0, 0, 1) ไป (0, -1, 0) — ค่าของลูกคือค่าเทียบพ่อ เสมอลองสร้างกระดูกสองท่อนต่อกัน แล้วหมุนแค่ท่อนล่าง:
=== ท่าพัก — ยังไม่หมุนอะไร ===
upper_arm rotation_euler ที่เก็บไว้ = (0.0, 0.0, 0.0) องศา
upper_arm หัวกระดูกในพิกัดโลก = (0.0, 0.0, 0.0)
forearm rotation_euler ที่เก็บไว้ = (0.0, 0.0, 0.0) องศา
forearm หัวกระดูกในพิกัดโลก = (0.0, 0.0, 1.0)
=== หมุนเฉพาะ upper_arm 90 องศารอบแกน X ===
upper_arm rotation_euler ที่เก็บไว้ = (90.0, -0.0, 0.0) องศา
upper_arm หัวกระดูกในพิกัดโลก = (0.0, 0.0, 0.0)
forearm rotation_euler ที่เก็บไว้ = (0.0, 0.0, 0.0) องศา ← ยัง 0 อยู่
forearm หัวกระดูกในพิกัดโลก = (0.0, -1.0, -0.0) ← แต่ย้ายที่แล้วforearm ไม่เคยถูกแตะ ค่าหมุนของมันยังเป็นศูนย์ทุกแกน แต่มันย้ายจาก (0, 0, 1) ไปอยู่ (0, -1, 0)
ค่าของกระดูกลูกไม่เคยเป็นตำแหน่งจริง มันคือตำแหน่ง เทียบกับพ่อ เสมอ
7.1 ทำไมออกแบบแบบนี้
เพราะมันตรงกับสิ่งที่คุณอยากสั่งจริง ๆ เวลาขยับร่างกาย
ตอนคุณงอศอก คุณไม่ได้คิดว่า "ย้ายมือไปพิกัด (0.3, -0.5, 1.2)" คุณคิดว่า "งอศอก 40 องศา" — แล้วมือก็ไปเอง เพราะมือติดอยู่กับปลายแขน ระบบพิกัดซ้อนกันจึงเป็นการเข้ารหัสความจริงข้อนี้ลงในโครงสร้างข้อมูล
7.2 ผลที่ตามมา ที่ต้องรู้ล่วงหน้า
เมื่อค่าของลูกเป็นค่าเทียบพ่อ สองอย่างนี้จะตามมาเสมอ:
- ความผิดพลาดสะสมลงข้างล่าง — หมุนไหล่ผิด 5 องศา ปลายนิ้วเคลื่อนไปไกลกว่านั้นมาก
- แก้ปลายทางไม่ได้ตรง ๆ — อยากให้มือไปแตะจุดหนึ่ง ต้องไล่หมุนทีละข้อจากต้นทาง ซึ่งช้าและน่ารำคาญ
ข้อหลังนี่เองคือปัญหาที่ IK เกิดมาเพื่อแก้ (บทที่ 9) แต่ก่อนจะไปถึงตรงนั้น ต้องผ่านจุดที่มือใหม่พังบ่อยที่สุดก่อน
Skinning กับ weight — จุดที่มือใหม่พังบ่อยที่สุด#
ตอนนี้มีกระดูกแล้ว แต่กระดูกกับเนื้อยังไม่รู้จักกัน คำถามคือ จุดแต่ละจุดบนผิวรู้ได้ยังไงว่าต้องตามกระดูกตัวไหน
ความเข้าใจผิดที่พังงานคนมานักต่อนัก: คิดว่าแต่ละจุดถูก "แบ่ง" ให้กระดูกตัวใดตัวหนึ่งเป็นเจ้าของ ถ้าเป็นแบบนั้นจริง รอยต่อทุกข้อจะขาดออกจากกันเวลางอ
ของจริงคือ แต่ละจุดฟังกระดูกหลายตัวพร้อมกัน โดยมีน้ำหนักคนละเท่าไหร่ นั่นคือ weight
ลองผูกแท่งทรงกระบอกเข้ากับกระดูกสองท่อน (ข้อต่ออยู่ที่ความสูง 1.0) แล้วอ่านค่า weight จริงไล่จากล่างขึ้นบน:
z=+0.00 lower=1.000 upper=0.000
z=+0.50 lower=0.995 upper=0.000
z=+0.83 lower=0.896 upper=0.104
z=+1.00 lower=0.500 upper=0.500 ← ตรงข้อต่อพอดี ฟังคนละครึ่งเป๊ะ
z=+1.17 lower=0.104 upper=0.896
z=+1.50 lower=0.000 upper=0.995
z=+2.00 lower=0.000 upper=1.000ตรงข้อต่อได้ 0.500 / 0.500 พอดี แล้วไล่ระดับออกไปทั้งสองทางอย่างสมมาตร นี่ไม่ใช่การแบ่งเจ้าของ แต่คือการผสม
0.500 / 0.500 พอดี จึงเคลื่อนครึ่งทางระหว่างอยู่นิ่งกับตามไปเต็ม — นั่นคือเหตุผลที่ข้อพับโค้ง ไม่ใช่หักเป็นมุม8.1 ผลของการผสม เห็นเป็นตัวเลข
ทีนี้หมุนกระดูกบน 90 องศา แล้ววัดว่าแต่ละความสูงเคลื่อนไปไหน:
ก่อน z=+0.50 -> หลัง (x=+0.141, y=+0.141, z=+0.500) ← ไม่ขยับตามเลย
ก่อน z=+1.00 -> หลัง (x=+0.141, y=+0.071, z=+1.071) ← ขยับครึ่งทาง
ก่อน z=+1.50 -> หลัง (x=+0.000, y=-0.500, z=+1.200) ← ตามไปเต็ม
ก่อน z=+2.00 -> หลัง (x=+0.000, y=-1.000, z=+1.200) ← ตามไปเต็มจุดที่ weight เป็น 0.5/0.5 ไปหยุดครึ่งทางระหว่าง "อยู่ที่เดิม" กับ "ตามไปเต็ม" พอดี — นี่คือเหตุผลที่ข้อพับดูโค้งแทนที่จะหักเป็นมุม
8.2 ทำไมมันถึงพังบ่อย
เพราะ automatic weights เดาจากระยะทางเชิงเรขาคณิต ไม่ได้เข้าใจว่าอะไรเป็นอะไร พอสองส่วนของโมเดลอยู่ใกล้กันในอวกาศแต่ควรขยับคนละทาง มันจะเดาผิดทันที — เช่น ต้นขาสองข้างที่ชิดกัน หรือแขนที่แนบลำตัว
อาการที่เห็นคือ "ขยับแขนแล้วเอวยุบตาม" ซึ่งไม่ใช่บั๊ก แต่คือ weight บนเอวติดค่าของกระดูกแขนมานิดหน่อย
เคล็ดลับ: เวลาเจออาการแบบนี้ อย่าเพิ่งไปแก้กระดูก ให้เข้าโหมด Weight Paint แล้วเลือกกระดูกที่ต้องสงสัย จุดที่ติดสีอยู่ผิดที่จะโผล่ให้เห็นทันที ปัญหาเกือบทั้งหมดของ "เนื้อบิดประหลาด" อยู่ที่ weight ไม่ใช่ที่โครงกระดูก
IK กับ FK — สองวิธีตอบคำถามเดียวกัน#
บทที่ 7 ทิ้งปัญหาไว้ข้อหนึ่ง: ค่ากระดูกเป็นค่าเทียบพ่อ อยากให้มือไปแตะจุดหนึ่งต้องไล่หมุนทีละข้อจากต้นทาง
วิธีไล่หมุนจากต้นทางลงปลายทางนั้นมีชื่อ เรียกว่า FK (Forward Kinematics) — คุณให้มุม ระบบคำนวณตำแหน่งปลายทางให้
IK (Inverse Kinematics) กลับทิศ — คุณให้ตำแหน่งปลายทาง ระบบคำนวณมุมทุกข้อย้อนกลับให้
ลองของจริง สร้างแขนสองท่อน ใส่ IK ชี้ไปที่เป้า แล้วย้ายเป้าอย่างเดียว ไม่หมุนกระดูกสักตัว:
เป้าที่ (1.0, 0, 1.2)
ปลายแขนไปหยุดที่ : (1.0, 0.0, 1.2)
upper_arm ค่าที่เก็บใน property = (0.0, -0.0, 0.0) มุมจริงหลังแก้สมการ = (90.0, 1.2, 0.0)
forearm ค่าที่เก็บใน property = (0.0, -0.0, 0.0) มุมจริงหลังแก้สมการ = (90.0, 78.4, 0.0)
เป้าที่ (1.8, 0, 0.5)
ปลายแขนไปหยุดที่ : (1.8, 0.0, 0.5)
upper_arm ค่าที่เก็บใน property = (0.0, -0.0, 0.0) มุมจริงหลังแก้สมการ = (90.0, 53.6, 0.0)
forearm ค่าที่เก็บใน property = (0.0, -0.0, 0.0) มุมจริงหลังแก้สมการ = (90.0, 95.4, -0.0)ปลายแขนไปหยุดตรงเป้าเป๊ะทุกครั้ง และมุมที่ Blender แก้สมการได้ก็เปลี่ยนไปทุกครั้ง — 78.4 องศา แล้ว 95.4 องศา
แต่ดูคอลัมน์กลางให้ดี: ค่าที่เก็บใน property เป็นศูนย์ตลอด ทุกครั้ง ทุกกระดูก
9.1 ปริศนาที่เพิ่งโผล่
Blender คำนวณมุมได้ 78.4 องศา แล้วเอาไปวาดจริง แต่ไม่เคยเขียนตัวเลขนั้นกลับลงในช่องเก็บค่า ทำไม
นี่คือเบาะแสชิ้นแรกของกฎที่จะไขในบทที่ 10 และปิดจบในบทที่ 13 — IK คือ constraint ตัวหนึ่ง และ constraint ไม่มีสิทธิ์เขียนทับค่าที่คุณเก็บไว้
9.2 ขีดจำกัดที่มองเห็นได้
บรรทัดสุดท้ายของการทดลองบอกอะไรบางอย่าง:
เป้าที่ (0.0, 0, 3.0)
ปลายแขนไปหยุดที่ : (0.0, 0.0, 2.0)เป้าอยู่ที่ระยะ 3.0 แต่แขนยาวรวมแค่ 2.0 มันจึงเหยียดสุดแล้วหยุด ไม่ยืดตาม IK ไม่ใช่เวทมนตร์ มันแก้สมการภายใต้ข้อจำกัดที่มีอยู่จริง
9.3 เลือกอันไหน
| ใช้ FK เมื่อ | ใช้ IK เมื่อ |
|---|---|
| ปลายทางลอยอิสระ เช่น แขนแกว่งตอนเดิน | ปลายทางต้องติดอยู่กับอะไร เช่น เท้าเหยียบพื้น มือจับแก้ว |
| อยากคุมรูปทรงของส่วนโค้ง | อยากคุมตำแหน่งจุดเดียว |
| ค่าที่คีย์ = สิ่งที่เห็น ตรงไปตรงมา | ค่าที่คีย์ = ตำแหน่งเป้า ไม่ใช่มุมข้อ |
แถวสุดท้ายคือสิ่งที่ทำให้คนสับสน: ตอนใช้ IK คุณคีย์ เป้า ไม่ได้คีย์กระดูก เพราะกระดูกไม่มีค่าอะไรให้คีย์ — ตามที่เห็นในตัวเลขข้างบน
LEVEL 3 · ระดับสูง
Constraint — ค่าที่ถูกเขียนทับหลังคุณคีย์ไปแล้ว#
ถึงเวลาไขปริศนาที่ปลูกไว้ตั้งแต่บทที่ 1
สร้างกล่องหนึ่งใบ คีย์ตำแหน่งไว้ที่จุดกำเนิดชัด ๆ แล้วใส่ constraint ชนิด Copy Location ชี้ไปที่เป้าหมายที่ (7, 3, 2):
=== ก่อนใส่ constraint ===
ค่าใน property (box.location) : (0.0, 0.0, 0.0)
ตำแหน่งจริงหลังประเมิน (matrix_world) : (0.0, 0.0, 0.0)
=== หลังใส่ Copy Location -> Target(7, 3, 2) ===
ค่าใน property (box.location) : (0.0, 0.0, 0.0) ← ไม่ขยับเลยสักนิด
ตำแหน่งจริงหลังประเมิน (matrix_world) : (7.0, 3.0, 2.0) ← แต่ของไปอยู่โน่นแล้วนี่คือคำตอบ กล่องอยู่ที่ (7, 3, 2) จริง แต่ค่าที่คุณคีย์ไว้ยังเป็น (0, 0, 0) ครบถ้วนไม่ถูกแตะ
constraint ไม่แก้ค่าของคุณ มันทำงานทีหลัง บนผลลัพธ์ที่คำนวณเสร็จแล้ว
10.1 ทำไมนี่คือคำอธิบายของอาการยอดฮิต
อาการที่ทุกคนเคยเจอ: เลื่อนค่าใน N-panel แล้วของไม่ขยับ หรือคีย์ตำแหน่งไว้แล้วของดื้อไปอยู่ที่อื่น
เกือบทุกครั้งคำตอบคือมี constraint ตัวหนึ่งทำงานอยู่ท้ายสาย และมันชนะเสมอ เพราะมันได้คิวหลัง
สิ่งที่ควรทำเมื่อเจออาการนี้ ไม่ใช่การไล่ลบคีย์ แต่คือเปิดแท็บ Object Constraint Properties ดูว่ามีอะไรอยู่ในนั้น
10.2 ทำไมออกแบบให้ไม่เขียนทับ
เพราะถ้า constraint เขียนกลับลง property ค่าที่คุณตั้งใจคีย์จะถูกทำลายถาวรทันทีที่เปิดไฟล์ และคุณจะย้อนกลับไม่ได้เลย
การแยกสองชั้นนี้ออกจากกันทำให้ ปิด constraint แล้วของกลับไปที่เดิมทันที ซึ่งเป็นสิ่งที่ทำให้มันปลอดภัยพอจะใช้จริง
💡 อุปมา: ค่าที่คุณคีย์ = สิ่งที่คุณพิมพ์ลงในเอกสาร · constraint = ตัวกรองที่แสดงผลทับตอนพิมพ์ออกกระดาษ — ต้นฉบับไม่เคยเปลี่ยน จุดที่อุปมานี้ใช้ไม่ได้ คือ constraint ไม่ได้ทำงานแค่ตอน "พิมพ์ออก" มันทำงานทุกเฟรม และผลของมันเป็นข้อมูลที่ constraint ตัวถัดไปเอาไปใช้ต่อได้
10.3 ลำดับใน stack มีความหมาย
constraint หลายตัวบนวัตถุเดียวกันทำงานเรียงจากบนลงล่าง ตัวล่างรับผลของตัวบนมาแล้วทำต่อ สลับลำดับ = ได้ผลคนละอย่าง
นี่คือเหตุผลที่ constraint stack เป็น "รายการที่เรียงได้" ไม่ใช่ "กล่องติ๊กถูก"
Driver — property ตัวหนึ่งอ่านค่าจากอีกตัว#
จนถึงตอนนี้ ค่ามาจากสองทาง: เส้นโค้งที่ผูกกับเวลา (บทที่ 3) และ constraint ที่ทำงานทับทีหลัง (บทที่ 10)
ยังมีทางที่สาม — ให้ property ตัวหนึ่งเลิกเก็บค่าของตัวเอง แล้วไปอ่านจาก property ตัวอื่นแทน นั่นคือ driver
ตั้งให้ scale.z ของกล่อง อ่านจาก location.x ของตัวควบคุม ด้วยสูตร 1 + lift * 0.5:
Ctrl.location.x = 0.0 -> Box.scale.z = 1.000
Ctrl.location.x = 1.0 -> Box.scale.z = 1.500
Ctrl.location.x = 2.0 -> Box.scale.z = 2.000
Ctrl.location.x = 4.0 -> Box.scale.z = 3.000ในไฟล์นี้ไม่มี keyframe แม้แต่จุดเดียว แต่ค่าเปลี่ยนทุกครั้งที่ขยับตัวควบคุม
11.1 ต่างจาก keyframe ตรงไหน
| keyframe | driver | |
|---|---|---|
| ค่าขึ้นกับ | เวลา (เลขเฟรม) | property ตัวอื่น |
| หยุดเวลาแล้ว | ค่าค้างนิ่ง | ยังเปลี่ยนได้ ถ้าต้นทางเปลี่ยน |
| ปลายทางระบุด้วย | data path | data path เหมือนกัน |
แถวสุดท้ายคือจุดที่บทที่ 2 กลับมาจ่ายคืน — ตอนตั้ง driver คุณต้องบอกให้ได้ว่าปลายทางคือ data path ไหน index อะไร ในตัวอย่างนี้คือ 'scale' index 2
11.2 ใช้จริงตอนไหน
เมื่อมี "ความสัมพันธ์" ที่ควรเป็นจริงตลอดเวลา ไม่ใช่แค่ในช่วงเวลาหนึ่ง เช่น
- ลูกบิดหมุน แล้วประตูเปิดตาม
- ตัวเลื่อนตัวเดียวคุมการยิ้มของหน้าทั้งหน้า
- ล้อหมุนสัมพันธ์กับระยะที่รถวิ่ง โดยไม่ต้องคีย์ล้อเลย
ข้อสุดท้ายคือตัวอย่างที่เห็นค่าชัดที่สุด: ถ้าคีย์ล้อเอง พอแก้ความเร็วรถทีหลัง ล้อจะลื่นไถลทันที แต่ถ้าใช้ driver มันถูกเสมอโดยอัตโนมัติ
เคล็ดลับ: ถ้าพบว่าตัวเองกำลังจะคีย์ของสองอย่างให้ตรงกันเป๊ะ ๆ นั่นคือสัญญาณว่าควรใช้ driver แทน
11.3 ข้อควรระวัง
driver ที่อ้างวนกลับมาหาตัวเอง (A อ่าน B, B อ่าน A) จะทำให้ Blender ไม่รู้ว่าต้องคำนวณใครก่อน — และนั่นคือคำถามที่บทถัดไปตอบทั้งบท
Shape key กับ Lattice — เปลี่ยนรูปโดยไม่แตะกระดูก#
ไม่ใช่ทุกการเปลี่ยนรูปที่เหมาะกับกระดูก การยิ้ม การขมวดคิ้ว การพองของลูกโป่ง — สิ่งเหล่านี้ไม่มี "ข้อต่อ" ให้หมุน
Shape key เก็บ ปลายทางของทุกจุด ไว้เป็นชุด แล้วให้เลขตัวเดียวคุมว่าจะเดินทางไปถึงกี่เปอร์เซ็นต์
=== ข้อมูลที่ถูกเก็บ ===
key 'Basis' : จุดที่ 0 อยู่ที่ (-1.0, -1.0, 0.0)
key 'Smile' : จุดที่ 0 อยู่ที่ (-1.0, -1.0, 1.0)
=== เลื่อนสไลเดอร์ตัวเดียว แล้วดูจุดจริง ===
Smile = 0.0 -> จุดที่ 0 อยู่ที่ z = 0.000
Smile = 0.25 -> จุดที่ 0 อยู่ที่ z = 0.250
Smile = 0.5 -> จุดที่ 0 อยู่ที่ z = 0.500
Smile = 0.75 -> จุดที่ 0 อยู่ที่ z = 0.750
Smile = 1.0 -> จุดที่ 0 อยู่ที่ z = 1.000เป็นการผสมเชิงเส้นตรงไปตรงมา ไม่มีอะไรซับซ้อนกว่านั้น
12.1 จุดที่มันเชื่อมกับสองบทก่อน
ค่าของ shape key คือ property ตัวหนึ่ง — แปลว่ามันคีย์ได้ (บทที่ 3) และมันถูก driver ขับได้ (บทที่ 11)
นั่นคือวิธีที่ระบบใบหน้าของงานจริงทำงาน: กระดูกตัวควบคุมหนึ่งตัวเลื่อนขึ้น → driver อ่านค่านั้น → shape key หลายตัวขยับตามสัดส่วนที่กำหนด กลไกสามอย่างจากสามบทต่อกันเป็นสายเดียว
12.2 Lattice — กรงที่บีบของข้างในตาม
Lattice คือกรงจุดควบคุมหยาบ ๆ ที่ครอบวัตถุไว้ ขยับจุดบนกรง แล้วทุกอย่างข้างในบิดตามอย่างนุ่มนวล
ลองพิสูจน์ด้วยตาเอง — ครอบท่อด้วย lattice แล้วขยับจุดควบคุมบนสุดไปทางแกน X:
จุดบนสุดใน mesh ต้นฉบับ = (0.0, 0.3, 1.0)
ผลหลังประเมิน ก่อนขยับ lattice = (0.0, 0.3, 1.0) ← ยังไม่มีอะไรเกิดขึ้น
ผลหลังประเมิน หลังขยับจุดควบคุม = (0.172, 0.3, 1.0) ← เนื้อบิดตามแล้ว
mesh ต้นฉบับหลังทุกอย่าง = (0.0, 0.3, 1.0) ← ไม่เคยถูกแตะเลย
จำนวนกระดูกในซีน = 0สองบรรทัดสุดท้ายคือหัวใจ เนื้อบิดจริงแต่ข้อมูล mesh ต้นฉบับไม่เปลี่ยนเลยสักจุด และทำได้โดยไม่มีกระดูกอยู่ในซีนแม้แต่ตัวเดียว — กฎเดียวกับ constraint ในบทที่ 10 เป๊ะ คือทำงานบนผลลัพธ์ ไม่ใช่บนค่าที่เก็บ
มันมีอยู่เพราะบางครั้งคุณอยากบิด ทั้งก้อน โดยไม่สนใจว่าข้างในมีกี่จุด เช่น ทำให้ตัวหนังสือทั้งบรรทัดโค้งเป็นคลื่น — ถ้าใช้ shape key ต้องกำหนดปลายทางทุกจุดเอง ซึ่งไม่มีใครทำ
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
ส่งท้าย — และไปต่อตรงไหน#
ถ้าคุณอ่านมาถึงตรงนี้ สิ่งที่เปลี่ยนไปไม่ใช่จำนวนปุ่มที่กดเป็น แต่เป็นคำถามที่คุณถามเวลาของไม่เป็นอย่างที่คิด
จากเดิม "ทำไมมันไม่ขยับ" เป็น "ค่านี้ถูกคำนวณที่ชั้นไหน และใครได้คิวหลังกว่า" — คำถามหลังนี้ตอบได้เสมอ และนั่นคือทั้งหมดที่เล่มนี้สัญญาไว้หน้าแรก
14.1 ของที่เหลือในเอกสารต้นทาง และมันอยู่ตรงไหนของแผนที่
เอกสาร Animation and Rigging ของ Blender ยังมีอีกหลายหัวข้อที่เล่มนี้ไม่ลงลึก ตอนนี้คุณมีแผนที่พอจะวางมันเองได้แล้ว:
| หัวข้อ | มันคืออะไรในภาษาของเล่มนี้ |
|---|---|
| Markers | ป้ายบอกเวลาบนไทม์ไลน์ ไม่ได้ให้ค่าอะไรกับใคร เป็นเครื่องมือของคนทำงาน ไม่ใช่ของกลไก |
| Motion Paths | เส้นที่วาดจากการประเมินหลายเฟรมมาต่อกัน — เป็นการ แสดงผล ไม่ใช่แหล่งของค่า |
| In-Betweens | เครื่องมือช่วยปักคีย์ระหว่างสองคีย์ ทำงานอยู่ที่ชั้น F-Curve ล้วน ๆ |
| NLA | ชั้นที่เอา Action หลายก้อนมาเรียงและผสมกัน — มันตัดสินว่า F-Curve ชุดไหนถูกใช้ จึงอยู่ ต้นทาง ของชั้นที่เขียนลง property (กฎข้อ 1 บทที่ 13) |
| Bone Constraints ตัวอื่น ๆ | เป็น constraint ทั้งหมด จึงอยู่ใต้กฎข้อ 1 เหมือนกันทุกตัว — ทำงานบนผลลัพธ์ ไม่เขียนกลับลง property ไม่มีข้อยกเว้น |
| Keying Sets | ทางลัดสำหรับคีย์หลาย data path พร้อมกัน — เป็นเรื่องความสะดวก ไม่ใช่กลไกใหม่ |
สังเกตว่าไม่มีอันไหนเลยที่ต้องใช้กฎใหม่ — ทุกอันตอบได้ด้วยกฎสามข้อในบทที่ 13 คือ ใครเขียนลง property ได้ · ใครอ่านค่าจากใคร · และในกองเดียวกันเรียงยังไง นั่นคือสัญญาณว่าแบบจำลองความคิดที่คุณมีตอนนี้ ใหญ่พอจะรับของที่เหลือได้
14.2 ลองด้วยตัวเองต่อ
ทุกผลรันในเล่มนี้ทำซ้ำได้จากเครื่องคุณเอง โดยไม่ต้องเปิดหน้าต่าง Blender เลย:
blender --background --factory-startup --python script.pyการอ่านค่าออกมาเป็นตัวเลขแบบนี้คือวิธีเรียนที่เร็วที่สุด เพราะมันตัดการเดาออกหมด — เห็นว่า property เป็นเท่าไหร่ เห็นว่าผลจริงเป็นเท่าไหร่ แล้วเห็นว่ามันไม่เท่ากันตอนไหน
อ่านต่อ: HyperFrames Handbook — เมื่อคุณเข้าใจแล้วว่าเฟรมคือค่าที่ถูกคำนวณใหม่ทุกครั้ง คำถามถัดไปคือ ต้องลาก 3D เข้ามาเมื่อไหร่ และเมื่อไหร่เขียนเป็น HTML ก็พอ เล่มนั้นตอบด้านที่เบากว่าของคำถามเดียวกัน
ถ้าเนื้อหานี้มีประโยชน์ —เลี้ยงกาแฟสักแก้ว