Entri
Menurut saya, kita perlu menggunakan lebih sedikit trigonometri dalam grafik komputer. Pemahaman yang baik tentang proyeksi, refleksi, dan operasi vektor (seperti dalam arti sebenarnya dari skalar (dot) dan produk vektor (lintas) vektor) biasanya datang dengan rasa kecemasan yang tumbuh ketika menggunakan trigonometri. Lebih tepatnya, saya percaya bahwa trigonometri baik untuk memasukkan data ke dalam algoritma (untuk konsep sudut, ini adalah cara intuitif untuk mengukur orientasi), saya merasa ada sesuatu yang salah ketika saya melihat trigonometri terletak di kedalaman beberapa algoritma render 3D. Bahkan, saya pikir di suatu tempat anak kucing mati ketika trigonometri merayap di sana. Dan saya tidak begitu khawatir tentang kecepatan atau akurasi, tetapi dengan keanggunan konseptual, saya pikir ... Sekarang saya akan menjelaskan.
Di tempat lain, saya sudah membahas bahwa skalar dan produk vektor vektor mengandung semua informasi yang diperlukan untuk belokan, untuk dua operasi "persegi panjang" itu - sinus dan kosinus sudut. Informasi ini setara dengan sinus dan cosinus di sejumlah besar tempat sehingga Anda dapat menggunakan produk vektor dan menyingkirkan trigonometri dan sudut. Dalam praktiknya, Anda dapat melakukan ini dengan tetap dalam vektor Euclidean biasa, tanpa trigonometri sama sekali. Ini membuat kita bertanya-tanya: "Apakah kita tidak melakukan sesuatu yang berlebihan?" Tampaknya sedang melakukan. Namun, sayangnya, bahkan para profesional yang berpengalaman pun cenderung menyalahgunakan trigonometri dan membuat hal-hal menjadi sangat kompleks, rumit dan bukan yang paling ringkas. Dan bahkan mungkin "salah."
Mari kita berhenti membuat artikel lebih abstrak. Mari kita bayangkan salah satu kasus mengganti formula trigonometri dengan produk vektor dan melihat apa yang baru saja saya bicarakan.
Opsi yang salah untuk memutar spasi atau objek
Mari kita memiliki fungsi yang menghitung matriks rotasi dari vektor di sekitar vektor yang dinormalisasi
di sudut
. Di mesin 3D atau perpustakaan matematika waktu-nyata apa pun, akan ada satu fungsi yang kemungkinan besar disalin secara membabi buta dari mesin lain, tutorial Wikipedia atau OpenGL ... (ya, pada titik ini Anda harus mengakui, dan tergantung pada suasana hati Anda, ada kemungkinan untuk khawatir dari karena ini).
Fungsi akan terlihat seperti ini:
mat3x3 rotationAxisAngle( const vec3 & v, float a ) { const float si = sinf( a ); const float co = cosf( a ); const float ic = 1.0f - co; return mat3x3( vx*vx*ic + co, vy*vx*ic - si*vz, vz*vx*ic + si*vy, vx*vy*ic + si*vz, vy*vy*ic + co, vz*vy*ic - si*vx, vx*vz*ic - si*vy, vy*vz*ic + si*vx, vz*vz*ic + co ); }
Bayangkan Anda sedang menggali bagian dalam demo atau game, mungkin menyelesaikan semacam modul animasi, dan Anda perlu memutar objek ke arah yang ditentukan. Anda ingin memutarnya sehingga salah satu sumbunya, katakanlah, sumbu
bertepatan dengan vektor tertentu
, katakanlah, bersinggungan dengan jalur animasi. Anda, tentu saja, memutuskan untuk membuat matriks yang akan berisi transformasi menggunakan
rotationAxisAngle()
. Jadi, pertama-tama Anda perlu mengukur sudut antara sumbu
objek Anda dan vektor orientasi yang diinginkan. Karena Anda adalah seorang programmer grafis, Anda tahu bahwa ini dapat dilakukan dengan produk skalar dan kemudian mengekstraksi sudut dengan
acos()
.
Anda juga tahu bahwa kadang-kadang
acosf()
dapat mengembalikan nilai aneh jika produk skalar berada di luar kisaran [-1; 1], dan Anda memutuskan untuk mengubah nilainya sehingga masuk dalam kisaran ini (
kira-kira Per. Untuk menjepit) (pada titik ini Anda bahkan dapat berani menyalahkan keakuratan komputer Anda, karena panjang vektor yang dinormalisasi tidak tepat 1). Pada titik ini, satu anak kucing mati. Tetapi sampai Anda mengetahuinya, Anda terus menulis kode Anda. Selanjutnya, Anda menghitung sumbu rotasi, dan Anda tahu bahwa ini adalah produk vektor dari vektor
objek Anda dan arah yang dipilih
, semua titik di objek Anda akan berputar dalam bidang yang sejajar dengan yang ditentukan oleh dua vektor ini, untuk berjaga-jaga ... (anak kucing dihidupkan kembali dan dibunuh lagi). Akibatnya, kode tersebut terlihat seperti ini:
const vec3 axi = normalize( cross( z, d ) ); const float ang = acosf( clamp( dot( z, d ), -1.0f, 1.0f ) ); const mat3x3 rot = rotationAxisAngle( axi, ang );
Untuk memahami mengapa ini bekerja, tetapi masih salah, kami akan membuka semua kode
rotationAxisAngle()
dan melihat apa yang sebenarnya terjadi:
const vec3 axi = normalize( cross( z, d ) ); const float ang = acosf( clamp( dot( z, d ), -1.0f, 1.0f ) ); const float co = cosf( ang ); const float si = sinf( ang ); const float ic = 1.0f - co; const mat3x3 rot = mat3x3( axi.x*axi.x*ic + co, axi.y*axi.x*ic - si*axi.z, axi.z*axi.x*ic + si*axi.y, axi.x*axi.y*ic + si*axi.z, axi.y*axi.y*ic + co, axi.z*axi.y*ic - si*axi.x, axi.x*axi.z*ic - si*axi.y, axi.y*axi.z*ic + si*axi.x, axi.z*axi.z*ic + co);
Seperti yang mungkin telah Anda perhatikan, kami melakukan panggilan acos yang agak tidak akurat dan mahal untuk membatalkannya segera dengan menghitung kosinus nilai pengembalian. Dan pertanyaan pertama muncul: "mengapa tidak melewatkan rantai panggilan
acos()
--->
cos()
dan menghemat waktu CPU?" Selain itu, apakah ini tidak memberitahu kita bahwa kita melakukan sesuatu yang salah dan sangat rumit, dan bahwa beberapa prinsip matematika sederhana datang kepada kita yang memanifestasikan dirinya melalui penyederhanaan ungkapan ini?
Anda dapat berargumen bahwa penyederhanaan tidak dapat dilakukan, karena Anda akan membutuhkan sudut untuk menghitung sinus. Namun, ini tidak benar. Jika Anda terbiasa dengan produk vektor vektor, maka Anda tahu bahwa seperti produk skalar mengandung cosinus, vektor tersebut mengandung sinus. Sebagian besar programmer grafis memahami mengapa produk skalar dari vektor diperlukan, tetapi tidak semua orang mengerti mengapa produk vektor diperlukan (dan menggunakannya hanya untuk membaca normals dan sumbu rotasi). Pada dasarnya, prinsip matematika yang membantu kita menyingkirkan pasangan cos / acos juga memberi tahu kita bahwa di mana ada produk skalar, mungkin ada produk vektor yang melaporkan informasi yang hilang (bagian tegak lurus, sinus).
Cara yang tepat untuk memutar ruang atau objek
Sekarang kita dapat mengekstrak sinus sudut antara
dan
hanya dengan melihat panjang produk vektor mereka ... - ingat itu
dan
dinormalisasi! Dan ini berarti kita dapat (kita harus !!) menulis ulang fungsi dengan cara ini:
const vec3 axi = cross( z, d ); const float si = length( axi ); const float co = dot( z, d ); const mat3x3 rot = rotationAxisCosSin( axi/si, co, si );
dan pastikan bahwa fungsi konstruksi matriks rotasi baru kami,
rotationAxisCosSin()
, tidak menghitung sinus dan cosinus di mana pun, tetapi menganggapnya sebagai argumen:
mat3x3 rotationAxisCosSin( const vec3 & v, const float co, const float si ) { const float ic = 1.0f - co; return mat3x3( vx*vx*ic + co, vy*vx*ic - si*vz, vz*vx*ic + si*vy, vx*vy*ic + si*vz, vy*vy*ic + co, vz*vy*ic - si*vx, vx*vz*ic - si*vy, vy*vz*ic + si*vx, vz*vz*ic + co ); }
Ada satu hal lagi yang dapat dilakukan untuk menghilangkan normalisasi dan akar kuadrat - merangkum seluruh logika dalam satu fungsi baru dan meneruskan
1/si
ke matriks:
mat3x3 rotationAlign( const vec3 & d, const vec3 & z ) { const vec3 v = cross( z, d ); const float c = dot( z, d ); const float k = (1.0fc)/(1.0fc*c); return mat3x3( vx*vx*k + c, vy*vx*k - vz, vz*vx*k + vy, vx*vy*k + vz, vy*vy*k + c, vz*vy*k - vx, vx*vz*K - vy, vy*vz*k + vx, vz*vz*k + c ); }
Kemudian, Zoltan Vrana memperhatikan bahwa
k
dapat disederhanakan menjadi
k = 1/(1+c)
, yang tidak hanya terlihat secara matematis lebih elegan, tetapi juga memindahkan dua fitur ke k dan, dengan demikian, seluruh fungsi (
dan
paralel) menjadi satu (saat
dan
bertepatan dalam hal ini tidak ada rotasi yang jelas). Kode akhir terlihat seperti ini:
mat3x3 rotationAlign( const vec3 & d, const vec3 & z ) { const vec3 v = cross( z, d ); const float c = dot( z, d ); const float k = 1.0f/(1.0f+c); return mat3x3( vx*vx*k + c, vy*vx*k - vz, vz*vx*k + vy, vx*vy*k + vz, vy*vy*k + c, vz*vy*k - vx, vx*vz*K - vy, vy*vz*k + vx, vz*vz*k + c ); }
Kami tidak hanya menyingkirkan tiga fungsi trigonometri dan menyingkirkan penjepit jelek (dan normalisasi!), Tetapi juga secara konsep menyederhanakan matematika 3D kami. Tidak ada fungsi transendental, hanya vektor yang digunakan di sini. Vektor membuat matriks yang memodifikasi vektor lain. Dan ini penting, karena semakin sedikit trigonometri pada mesin 3D Anda, semakin cepat dan jernihnya, tetapi, pertama-tama, secara matematis lebih elegan (lebih benar!).