ฉันกำลังหมุนวัตถุบนสองแกนดังนั้นทำไมมันหมุนรอบแกนที่สามต่อไป?


38

ฉันเห็นคำถามเกิดขึ้นบ่อยครั้งที่มีปัญหาพื้นฐานนี้ แต่ทุกคำถามล้วนแล้วแต่เกี่ยวข้องกับคุณลักษณะหรือเครื่องมือที่กำหนด นี่คือความพยายามในการสร้างคำตอบที่ยอมรับได้ซึ่งเราสามารถอ้างถึงผู้ใช้เมื่อเกิดเหตุการณ์นี้ขึ้น - ด้วยตัวอย่างภาพเคลื่อนไหวมากมาย! :)


สมมติว่าเรากำลังสร้างกล้องตัวแรก แนวคิดพื้นฐานคือควรหันเหมองไปทางซ้ายและขวา ดังนั้นเราจึงเขียนโค้ดแบบนี้ (ใช้ Unity เป็นตัวอย่าง):

void Update() {
    float speed = lookSpeed * Time.deltaTime;

    // Yaw around the y axis using the player's horizontal input.        
    transform.Rotate(0f, Input.GetAxis("Horizontal") * speed, 0f);

    // Pitch around the x axis using the player's vertical input.
    transform.Rotate(-Input.GetAxis("Vertical") * speed,  0f, 0f);
}

หรืออาจจะ

// Construct a quaternion or a matrix representing incremental camera rotation.
Quaternion rotation = Quaternion.Euler(
                        -Input.GetAxis("Vertical") * speed,
                         Input.GetAxis("Horizontal") * speed,
                         0);

// Fold this change into the camera's current rotation.
transform.rotation *= rotation;

และส่วนใหญ่ใช้งานได้ แต่เมื่อเวลาผ่านไปมุมมองเริ่มคดเคี้ยว กล้องดูเหมือนจะเปิดแกนหมุน (z) แม้ว่าเราจะบอกให้หมุนบน x และ y เท่านั้น!

ตัวอย่างภาพเคลื่อนไหวของกล้องตัวแรกที่เอียงด้านข้าง

สิ่งนี้สามารถเกิดขึ้นได้หากเราพยายามจัดการวัตถุที่อยู่ด้านหน้ากล้อง - บอกว่ามันเป็นโลกที่เราต้องการหันมามอง:

ตัวอย่างภาพเคลื่อนไหวของโลกที่เอียงไปด้านข้าง

ปัญหาเดียวกัน - หลังจากผ่านไปครู่หนึ่งขั้วโลกเหนือเริ่มที่จะเดินไปทางซ้ายหรือขวา เราให้อินพุตกับสองแกน แต่เราได้ผลการหมุนที่สับสนในหนึ่งในสาม และเกิดขึ้นไม่ว่าเราจะใช้การหมุนทั้งหมดของเรารอบแกนท้องถิ่นของวัตถุหรือแกนโลกของโลก

ในเครื่องยนต์จำนวนมากคุณจะเห็นสิ่งนี้ในผู้ตรวจสอบ - หมุนวัตถุในโลกและทันใดนั้นตัวเลขก็เปลี่ยนไปบนแกนที่เราไม่ได้แตะเลย!

ตัวอย่างภาพเคลื่อนไหวแสดงการจัดการวัตถุที่อยู่ถัดจากการอ่านค่าของมุมการหมุน  การเปลี่ยนแปลงมุม z ถึงแม้ว่าผู้ใช้จะไม่จัดการกับแกนนั้น

ดังนั้นนี่เป็นข้อบกพร่องของเครื่องยนต์หรือไม่ เราจะบอกโปรแกรมที่เราไม่ต้องการให้เพิ่มการหมุนเวียนพิเศษได้อย่างไร

มันมีบางอย่างเกี่ยวกับมุมออยเลอร์หรือไม่? ฉันควรใช้ Quaternions หรือ Rotation Matrices หรือ Basis Vectors แทนหรือไม่



1
ฉันพบว่าคำตอบของคำถามต่อไปนี้มีประโยชน์มากเช่นกัน gamedev.stackexchange.com/questions/123535/…
Travis Pettry

คำตอบ:


51

ไม่มีสิ่งนี้ไม่ใช่ข้อผิดพลาดของเครื่องยนต์หรือสิ่งประดิษฐ์ที่แสดงถึงการหมุนเฉพาะ (สิ่งเหล่านั้นสามารถเกิดขึ้นได้เช่นกัน แต่เอฟเฟกต์นี้ใช้ได้กับทุกระบบที่แสดงถึงการหมุน

คุณได้ค้นพบข้อเท็จจริงที่แท้จริงเกี่ยวกับการหมุนในพื้นที่สามมิติและมันออกจากสัญชาตญาณของเราเกี่ยวกับการเปลี่ยนแปลงอื่น ๆ เช่นการแปล:

ตัวอย่างภาพเคลื่อนไหวแสดงให้เห็นว่าการใช้การหมุนตามลำดับที่แตกต่างกันจะให้ผลลัพธ์ที่ต่างกัน

เมื่อเราเขียนการหมุนบนแกนมากกว่าหนึ่งแกนผลลัพธ์ที่เราได้รับไม่ได้เป็นเพียงมูลค่ารวม / สุทธิที่เราใช้กับแต่ละแกน (ตามที่เราคาดหวังสำหรับการแปล) ลำดับที่เราใช้การหมุนจะเปลี่ยนผลลัพธ์เมื่อการหมุนแต่ละครั้งเคลื่อนที่แกนที่การหมุนครั้งถัดไปใช้ (ถ้าหมุนรอบแกนท้องถิ่นของวัตถุ) หรือความสัมพันธ์ระหว่างวัตถุกับแกน (ถ้าหมุนรอบโลก แกน)

การเปลี่ยนความสัมพันธ์ของแกนในช่วงเวลาหนึ่งอาจทำให้สัญชาตญาณของเราสับสนเกี่ยวกับสิ่งที่แต่ละ "แกน" ควรทำ โดยเฉพาะอย่างยิ่งการรวมกันของการหันเหและหันเหระดับเสียงให้ผลเช่นเดียวกับการหมุนม้วน!

ตัวอย่างภาพเคลื่อนไหวที่แสดงลำดับของ pitch pitch-yaw-pitch ในท้องที่ให้เอาต์พุตเช่นเดียวกับ roll ในตัวเดียว

คุณสามารถตรวจสอบได้ว่าแต่ละขั้นตอนมีการหมุนอย่างถูกต้องเกี่ยวกับแกนที่เราร้องขอ - ไม่มีความผิดพลาดของเครื่องยนต์หรือสิ่งประดิษฐ์ในสัญกรณ์ของเรารบกวนหรือคาดเดาอินพุตของเรา - ลักษณะการหมุนของทรงกลม (หรือ hyperspherical รอบ "สู่กัน พวกเขาอาจจะตั้งฉากในพื้นที่สำหรับการหมุนเล็ก ๆ แต่เมื่อพวกเขาซ้อนเราพบว่าพวกเขาไม่ได้เป็นมุมฉากทั่วโลก

นี่เป็นสิ่งที่น่าทึ่งและชัดเจนสำหรับการหมุน 90 องศาเช่นเดียวกับที่กล่าวมาข้างต้น แต่แกนที่หลงทางนั้นคืบคลานไปในการหมุนเล็ก ๆ หลายครั้งเช่นกันตามที่แสดงในคำถาม

แล้วเราจะทำอย่างไรกับมัน

หากคุณมีระบบการหมุนระยะพิทซ์แล้ววิธีที่เร็วที่สุดวิธีหนึ่งในการกำจัดการม้วนที่ไม่ต้องการคือการเปลี่ยนการหมุนแบบใดแบบหนึ่งเพื่อให้ทำงานบนแกนการเปลี่ยนแปลงทั่วโลกหรือแกนหลักแทนแกนท้องถิ่นของวัตถุ ด้วยวิธีนี้คุณจะไม่ได้รับการปนเปื้อนข้ามระหว่างสองแกน - แกนเดียวยังคงถูกควบคุมอย่างสมบูรณ์

นี่คือลำดับเดียวกันของ pitch-yaw-pitch ที่กลายเป็นม้วนในตัวอย่างข้างต้น แต่ตอนนี้เราใช้การหันเหของเรารอบแกน Y ทั่วโลกแทนวัตถุ

ตัวอย่างภาพเคลื่อนไหวของเหยือกน้ำหมุนด้วยระดับท้องถิ่นและหันเหทั่วโลกโดยไม่มีปัญหาการหมุนตัวอย่างภาพเคลื่อนไหวของกล้องคนแรกโดยใช้เครื่องมือหาว

ดังนั้นเราจึงสามารถแก้ไขกล้องคนแรกด้วยมนต์ "Pitch Locally, Yaw Globally":

void Update() {
    float speed = lookSpeed * Time.deltaTime;

    transform.Rotate(0f, Input.GetAxis("Horizontal") * speed, 0f, Space.World);
    transform.Rotate(-Input.GetAxis("Vertical") * speed,  0f, 0f, Space.Self);
}

หากคุณกำลังรวมการหมุนของคุณโดยใช้การคูณคุณจะต้องสลับลำดับซ้าย / ขวาของการคูณอย่างใดอย่างหนึ่งเพื่อให้ได้เอฟเฟกต์เดียวกัน:

// Yaw happens "over" the current rotation, in global coordinates.
Quaternion yaw = Quaternion.Euler(0f, Input.GetAxis("Horizontal") * speed, 0f);
transform.rotation =  yaw * transform.rotation; // yaw on the left.

// Pitch happens "under" the current rotation, in local coordinates.
Quaternion pitch = Quaternion.Euler(-Input.GetAxis("Vertical") * speed, 0f, 0f);
transform.rotation = transform.rotation * pitch; // pitch on the right.

(คำสั่งซื้อที่เฉพาะเจาะจงจะขึ้นอยู่กับอนุสัญญาการคูณในสภาพแวดล้อมของคุณ แต่เหลือ = มากขึ้นทั่วโลก / ขวา = มากขึ้นในท้องถิ่นเป็นตัวเลือกที่พบบ่อย)

สิ่งนี้เทียบเท่ากับการจัดเก็บ yaw สุทธิทั้งหมดและ pitch ทั้งหมดที่คุณต้องการเป็นตัวแปร float จากนั้นใช้ผลลัพธ์สุทธิทั้งหมดในครั้งเดียวสร้าง quaternion ปฐมนิเทศหรือเมทริกซ์ใหม่จากมุมเหล่านี้เพียงอย่างเดียว (ให้คุณtotalPitchยึดไว้):

// Construct a new orientation quaternion or matrix from Euler/Tait-Bryan angles.
var newRotation = Quaternion.Euler(totalPitch, totalYaw, 0f);
// Apply it to our object.
transform.rotation = newRotation;

หรือเทียบเท่า ...

// Form a view vector using total pitch & yaw as spherical coordinates.
Vector3 forward = new Vector3(
                    Mathf.cos(totalPitch) * Mathf.sin(totalYaw),
                    Mathf.sin(totalPitch),
                    Mathf.cos(totalPitch) * Mathf.cos(totalYaw));

// Construct an orientation or view matrix pointing in that direction.
var newRotation = Quaternion.LookRotation(forward, new Vector3(0, 1, 0));
// Apply it to our object.
transform.rotation = newRotation;

การใช้การแยกแบบโกลบอล / โลคอลนี้การหมุนจะไม่มีโอกาสประสมและมีอิทธิพลต่อกันและกันเพราะมันถูกใช้กับแกนอิสระ

แนวคิดเดียวกันสามารถช่วยได้หากเป็นวัตถุในโลกที่เราต้องการหมุน สำหรับตัวอย่างเช่นโลกเรามักจะต้องการที่จะกลับด้านและใช้การหันเหของเราในพื้นที่ (ดังนั้นมันหมุนรอบเสาของมันเสมอ) และขว้างไปทั่วโลก (เพื่อให้มันเคล็ดลับไป / ออกจากมุมมองของเรามากกว่าไป / ออกจากออสเตรเลีย ทุกที่ที่มันชี้ ... )

ตัวอย่างภาพเคลื่อนไหวของลูกโลกที่หมุนได้ดีกว่า

ข้อ จำกัด

กลยุทธ์ไฮบริดทั่วโลก / ท้องถิ่นนี้ไม่ได้แก้ไขที่ถูกต้องเสมอไป ตัวอย่างเช่นในเกมที่มีเที่ยวบิน 3 มิติ / ว่ายน้ำคุณอาจต้องการชี้ตรงขึ้น / ลงและยังคงควบคุมได้เต็มที่ แต่ด้วยการตั้งค่านี้คุณจะได้รับการล็อคด้วย gimbalแกน yaw ของคุณ (ทั่วโลกขึ้นไป) จะขนานกับแกนม้วนของคุณ (ไปข้างหน้าในพื้นที่) และคุณไม่มีทางที่จะมองไปทางซ้ายหรือขวาโดยไม่ต้องบิด

สิ่งที่คุณสามารถทำได้แทนในกรณีเช่นนี้คือใช้การหมุนรอบตัวแบบบริสุทธิ์อย่างที่เราเริ่มต้นด้วยคำถามข้างต้น (ดังนั้นการควบคุมของคุณจะรู้สึกเหมือนกันไม่ว่าคุณจะมองที่ไหน) ซึ่งในขั้นต้นจะทำให้ม้วนเข้ามา - เราแก้ไขให้ถูกต้อง

ตัวอย่างเช่นเราสามารถใช้การหมุนเฉพาะที่เพื่ออัปเดตเวกเตอร์ "ไปข้างหน้า" ของเราจากนั้นใช้เวกเตอร์ไปข้างหน้าพร้อมกับการอ้างอิง "up" เวกเตอร์เพื่อสร้างการวางแนวสุดท้ายของเรา (ตัวอย่างเช่นการใช้วิธีQuaternion.LookRotationของ Unity หรือสร้างเมทริกซ์ orthonormal จากเวกเตอร์เหล่านี้ด้วยตนเอง) โดยการควบคุมเวกเตอร์อัพเราควบคุมม้วนหรือบิด

สำหรับตัวอย่างเที่ยวบิน / ว่ายน้ำคุณจะต้องใช้การแก้ไขเหล่านี้อย่างค่อยเป็นค่อยไปเมื่อเวลาผ่านไป ถ้ามันฉับพลันเกินไปมุมมองสามารถเย้ยหยันในทางที่เบี่ยงเบน แต่คุณสามารถใช้เวกเตอร์อัพปัจจุบันของผู้เล่นและบอกใบ้ไปทางแนวตั้งทีละเฟรมจนกระทั่งมุมมองของพวกเขาออกมา การใช้สิ่งนี้ในบางครั้งอาจทำให้เกิดความรำคาญน้อยกว่าการบิดกล้องในขณะที่การควบคุมของผู้เล่นไม่ได้ทำงาน


1
ฉันขอถามคุณได้ทำอย่างไรกับ GIF พวกเขาดูดี
Bálint

1
@ Bálintฉันใช้โปรแกรมฟรีชื่อLICEcap - มันช่วยให้คุณบันทึกส่วนหนึ่งของหน้าจอเป็น gif จากนั้นฉันตัดแต่งภาพเคลื่อนไหว / เพิ่มข้อความคำอธิบายภาพเพิ่มเติม / บีบอัด gif โดยใช้ Photoshop
DMGregory

คำตอบนี้เป็นเอกภาพเท่านั้นหรือไม่ มีคำตอบทั่วไปบนเว็บหรือไม่
posfan12

2
@ posfan12 โค้ดตัวอย่างใช้ไวยากรณ์ Unity เพื่อความเป็นรูปธรรม แต่สถานการณ์และคณิตศาสตร์ที่เกี่ยวข้องนำไปใช้อย่างเท่าเทียมกันทั่วกระดาน คุณมีปัญหาในการนำตัวอย่างไปใช้กับสภาพแวดล้อมของคุณหรือไม่?
DMGregory

2
คณิตศาสตร์มีอยู่แล้วด้านบน ตัดสินโดยการแก้ไขของคุณคุณเพียงแค่ทำผิดส่วนของมัน ดูเหมือนว่าคุณกำลังใช้ประเภทเวกเตอร์ที่กำหนดไว้ที่นี่ ซึ่งรวมถึงวิธีการสร้างเมทริกซ์การหมุนจากชุดของมุมตามที่อธิบายไว้ข้างต้น เริ่มต้นด้วยเมทริกซ์เอกลักษณ์และการโทรTCVector::calcRotationMatrix(totalPitch, totalYaw, identity)- นี่เทียบเท่ากับตัวอย่าง "ทั้งหมดในคราวเดียวออยเลอร์" ด้านบน
DMGregory

1

หลังจากการเลี้ยงดูบุตรและฟังก์ชั่นการหมุนนานถึง 16 ชั่วโมง lol

ฉันแค่อยากให้โค้ดว่างเปล่าโดยทั่วไปแล้วหมุนเป็นวงกลมแล้วก็พลิกหน้าเมื่อมันหมุน

หรือกล่าวอีกนัยหนึ่งเป้าหมายคือการหมุนหันเหและระยะห่างโดยไม่มีผลกับการหมุน

นี่คือตัวอย่างบางส่วนที่ใช้งานได้จริงในแอปพลิเคชันของฉัน

ในความสามัคคี:

  1. สร้างที่ว่างเปล่า เรียกว่า forYaw
  2. ให้เด็กว่างเปล่าที่เรียกว่า ForPitch
  3. ในที่สุดทำลูกบาศก์และย้ายไปที่ z = 5 (ไปข้างหน้า) ตอนนี้เราสามารถเห็นสิ่งที่เรากำลังพยายามที่จะหลีกเลี่ยงที่จะบิดก้อน(บังเอิญ "กลิ้ง" ในขณะที่เราหันเหและสนาม)

ตอนนี้การเขียนสคริปต์ใน Visual Studio ทำการตั้งค่าของคุณและจบลงด้วยสิ่งนี้ใน Update:

objectForYaw.Rotate(objectForYaw.up, 1f, Space.World);
    objectForPitch.Rotate(objectForPitch.right, 3f, Space.World);

    //this will work on the pitch object as well
    //objectForPitch.RotateAround
    //    (objectForPitch.position, objectForPitch.right, 3f);

โปรดทราบว่ามันยากสำหรับฉันเมื่อฉันพยายามเปลี่ยนลำดับการเลี้ยงดู หรือถ้าฉันเปลี่ยน Space.World สำหรับวัตถุระดับเสียงลูก (Yaw หลักไม่คิดว่าจะถูกหมุนผ่าน Space.Self) ทำให้เกิดความสับสน แน่นอนฉันสามารถใช้การชี้แจงบางอย่างเกี่ยวกับสาเหตุที่ฉันต้องแกนท้องถิ่น แต่ใช้มันผ่านอวกาศโลก - ทำไม Space.Self ทำลายทุกอย่าง?


ยังไม่ชัดเจนสำหรับฉันว่านี่มีวัตถุประสงค์เพื่อตอบคำถามหรือหากคุณขอความช่วยเหลือในการทำความเข้าใจว่าทำไมรหัสที่คุณเขียนนั้นทำงานในลักษณะนี้ หากเป็นคำถามหลังสุดคุณควรโพสต์คำถามใหม่โดยเชื่อมโยงไปยังหน้านี้เพื่อดูบริบทหากคุณต้องการ คำใบ้me.Rotate(me.right, angle, Space.World)นั้นเหมือนกับme.Rotate(Vector3.right, angle, Space.Selfและการแปลงแบบซ้อนของคุณนั้นเทียบเท่ากับตัวอย่าง "Pitch Locally, Yaw Globally" ในคำตอบที่ยอมรับ
DMGregory

เล็กน้อยทั้งคู่ บางส่วนที่จะแบ่งปันในคำตอบ (เช่นที่นี่เป็นสิ่งที่ทำงานให้ฉัน) และยังแนะนำคำถาม คำใบ้นั้นทำให้ฉันคิด ขอบคุณมาก!
Jeh

น่าอัศจรรย์นี่ไม่ใช่การโหวต หลังจากชั่วโมงที่อ่านคำตอบที่ครอบคลุมซึ่งแม้ว่าการศึกษายังไม่ได้ผลคำตอบง่ายๆของการใช้แกนหมุนวัตถุแทนแกน Vector3.right ทั่วไปคือทั้งหมดที่ใช้เพื่อให้การหมุนคงที่กับวัตถุ ไม่น่าเชื่อว่าสิ่งที่ฉันกำลังมองหาถูกฝังอยู่ที่นี่ด้วยคะแนนโหวต 0 และในหน้า 2 ของ Google ของคำถามที่คล้ายกันมากมาย
user4779
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.