دعونا نبني محرك رسومات ثلاثية الأبعاد: تحويلات خطية
() translation by (you can also view the original English article)
مرحبًا بكم في الجزء الثاني من سلسلة مشغلات الرسومات ثلاثية الأبعاد! في هذه المرة سنتحدث عن التحولات الخطية ، والتي سوف تسمح لنا بتغيير خصائص مثل دوران وتوسعة ناقلاتنا ، وننظر في كيفية تطبيقها على الفصول التي قمنا ببنائها بالفعل.
إذا لم تكن قد قرأت بالفعل الجزء الأول من هذه السلسلة ، أقترح عليك القيام بذلك الآن. فقط في حالة عدم التذكر ، إليك ملخص سريع لما أنشأناه في المرة السابقة:
1 |
Point Class |
2 |
{
|
3 |
Variables:
|
4 |
num tuple[3]; //(x,y,z) |
5 |
|
6 |
Operators:
|
7 |
Point AddVectorToPoint(Vector); |
8 |
Point SubtractVectorFromPoint(Vector); |
9 |
SubtractPointFromPoint(Point); |
10 |
|
11 |
Functions:
|
12 |
//draw a point at its position tuple with your favorite graphics API
|
13 |
drawPoint; |
14 |
}
|
15 |
|
16 |
Vector Class |
17 |
{
|
18 |
Variables:
|
19 |
num tuple[3]; //(x,y,z) |
20 |
|
21 |
Operators:
|
22 |
Vector AddVectorToVector(Vector); |
23 |
Vector SubtractVectorFromVector(Vector); |
24 |
}
|
هاتان الفئتان سيكونان أساس محرك الرسومات الخاص بنا بالكامل ، حيث يمثل الأول نقطة (موقع مادي داخل المساحة الخاصة بك) والثاني يمثل المتجه (المساحة / الحركة بين نقطتين).
لمناقشتنا حول التحولات الخطية ، يجب إجراء تغيير بسيط على فئة النقطة: بدلاً من إخراج البيانات إلى سطر وحدة تحكم كما كان من قبل ، استخدم واجهة برمجة تطبيقات الرسومات المفضلة لديك واستخدم الدالة رسم النقطة الحالية على الشاشة.
أسس التحولات الخطية
مجرد تحذير: تبدو معادلات التحول الخطي أسوأ بكثير مما هي عليه بالفعل. سيكون هناك بعض علم المثلثات المعنية ، ولكن ليس عليك أن تعرف بالفعل كيفية القيام بذلك المثلثات: سأشرح ما لديك لإعطاء كل وظيفة وماذا ستخرج ، وبالنسبة للأشياء الداخلية التي يمكنك استخدامها فقط أي آلة حاسبة أو مكتبة الرياضيات قد يكون لديك.
تأخذ كل التحولات الخطية هذا الشكل:
[\(B = F(A]\
ذلك أنه إذا كان لديك تحويل خطي تعمل \(F()\)، والمدخلات الخاصة بك هو الناقل \(A\)، ثم الإخراج الخاص بك سوف تكون متجهة \(B\).
يمكن تمثيل كل من هذه القطع-موجهات اثنين والدالة-كمصفوفة: الناقل \(B\) كمصفوفة 1 × 3، الناقل \(A\) كآخر 1 × 3 مصفوفة، وتحويل خطي \(F\) كمصفوفة 3 × 3 (مصفوفة تحويل).
هذا يعني أنه عندما تقوم بتوسيع المعادلة ، يبدو كالتالي:
\[
\تبدأ {bmatrix}
\\ {b_{0
\\{b_{1
{b_{2
{end{bmatrix\
=
{begin{bmatrix\
\\(f_{00} & f_{01} & f_{02
\\{f_{10} & f_{11} & f_{12
\\{f_{20} & f_{21} & f_{22
{end{bmatrix\
{begin{bmatrix\
\\(a_{0
\\{a_{1
{a_{2
{end{bmatrix\
\]
إذا كنت قد أخذت صف دراسي في علم المثلثات أو الجبر الخطي ، فمن المحتمل أنك بدأت تتذكر الكابوس الذي كان عبارة عن الرياضيات المصفوفة. لحسن الحظ ، هناك طريقة أبسط لكتابة هذه المعادلة لأخذ معظم المشاكل. تبدو هكذا:
\[
{begin{bmatrix\
\\{b_{0
\\{b_{1
{b_{2
{end{bmatrix\
=
{begin{bmatrix\
\\{f_{00}a_{0} + f_{01}a_{1} + f_{02}a_{2
\\{f_ {10} a_ {0} + f_ {11} a_ {1} + f_ {12} a_ {2
\\{f_{20}a_{0} + f_{21}a_{1} + f_{22}a_{2
{end{bmatrix\
\]
ومع ذلك ، يمكن تغيير هذه المعادلات من خلال الحصول على مدخلات ثانية ، كما هو الحال في حالات التدوير ، حيث يجب إعطاء كل من المتجه وكمية دورانه. دعونا نلقي نظرة على كيفية عمل التناوب.
تناوب
الدوران هو ، بحكم تعريفه ، حركة دائرية لكائن حول نقطة دوران. يمكن أن تكون نقطة الدوران لمساحتنا واحداً من ثلاثة احتمالات: إما طائرة XY ، أو طائرة XZ ، أو الطائرة YZ (حيث تتكون كل طائرة من اثنين من ناقلاتنا الأساسية التي ناقشناها في الجزء الأول من السلسلة ).



لدينا ثلاث نقاط من التناوب يعني أن لدينا ثلاث مصفوفات دوران منفصلة ، على النحو التالي:
مصفوفة دوران XY:
\[
{begin{bmatrix\
\\cos \theta & -sin \theta & 0
\\sin \theta & cos \theta & 0
0 & 0 & 1\\
{end{bmatrix\
\]
XZ rotation matrix:
\[
{begin{bmatrix\
cos \theta & 0 & sin \theta\\
0 & 1 & 0\\
-sin \theta & 0 & cos \theta
\\ نهاية {} bmatrix
\]
مصفوفة دوران YZ:
\[
تبدأ {bmatrix}
1 & 0 & 0\\
0 & cos \theta & -sin \theta\\
0 & sin \theta & cos \theta
\\ نهاية {} bmatrix
\]
لذا لتدوير نقطة (A ) حول مستوى XY بمقدار 90 درجة ( ( pi / 2 ) راديان - معظم مكتبات الرياضيات لها وظيفة لتحويل الدرجات إلى راديان) ، عليك اتباع هذه الخطوات:
\[
\\ تبدأ {} الانحياز
تبدأ {bmatrix}
//{b_{0
{b_{1\\
{b_{2
\\ نهاية {} bmatrix
& =
تبدأ {bmatrix}
cos \frac{\pi}{2} & -sin \frac{\pi}{2} & 0\\
sin \frac{\pi}{2} & cos \frac{\pi}{2} & 0\\
0 & 0 & 1
نهاية {} bmatrix
\\ تبدأ {bmatrix}
a_{0}\\
{a_{ 1\\
{a_{2
نهاية {bmatrix}
& =
\\ تبدأ {bmatrix}
cos \frac{\pi}{2}a_{0} + -sin \frac{\pi}{2}a_{1} + 0a_{2}\\
sin \frac{\pi}{2}a_{0} + cos \frac{\pi}{2}a_{1} + 0a_{2}\\
0a_{0} + 0a_{1} + 1a_{2}
\\ نهاية {bmatrix} \\\\
& =
\\ تبدأ {bmatrix}
{0a_{0} + -1a_{1} + 0a_{2\\
{1a_{0} + 0a_{1} + 0a_{2\\
{0a_{0} + 0a_{1} + 1a_{2
\\ نهاية {bmatrix} \\\\
& =
\\ تبدأ {bmatrix}
{-a_{1\\
{a_{0\\
{a_{2
\\ نهاية {} bmatrix
\\ نهاية {} الانحياز
\]
حتى إذا كانت النقطة الأولية \\ (A \\) هي \\ ((3،4،5) \\) ، فإن نقطة الإخراج الخاصة بك \\ (B \\) ستكون \\ ((- 4،3،5) \\).
تمرين: وظائف التناوب
كتمرين ، حاول إنشاء ثلاث وظائف جديدة للفئة Vector
. ينبغي للمرء أن تدور ناقلات حول الطائرة XY ، واحدة حول الطائرة YZ ، واحدة حول الطائرة XZ. يجب أن تحصل وظائفك على المقدار المطلوب من الدرجات للتدوير كمدخل ، وتقوم بإرجاع متجه كمخرج.
يجب أن يكون التدفق الأساسي لوظائفك كما يلي:
- خلق ناقل الإخراج.
- تحويل درجة المدخلات في شكل راديان.
- حل لكل جزء من مجموعة نواقل الإخراج باستخدام المعادلات الموضحة أعلاه.
- إرجاع ناقل الإخراج.
تدريج
التحجيم عبارة عن تحويل يقوم إما بتكبير أو تصغير كائن يعتمد على مقياس معين.
تنفيذ هذا التحويل بسيط إلى حد ما (على الأقل مقارنة بالتناوب). يتطلب تحجيم التحجيم مدخلين: مدخلاً للدخل ومجموعة قياس 3 ، والتي تحدد كيفية قياس متجه الإدخال في كل محور من محاور المساحة.
على سبيل المثال ، في tuple \\ (s_ {0} ، s_ {1} ، s_ {2}) \\) ، \\ (s_ {0} \\) يمثل المقياس على طول المحور X ، \\ (s_ {1} \\) على طول المحور Y و \\ (s_ {2} \\) على طول المحور Z.
مصفوفة التحجيم هي كما يلي (حيث (s_ {0} ) و (s_ {1} ) و (s_ {2} ) هي عناصر المقياس 3-tuple):
\[
\\ تبدأ {bmatrix}
s0 & 0 & 0\\
0 & s1 & 0\\
0 & 0 & s2
\\ نهاية {} bmatrix
\]
من أجل جعل المتجه المدخل A \\ ((a_ {0} ، a_ {1} ، a_ {2}) \\) ضعف الحجم على طول المحور X (أي ، باستخدام مقياس 3-tuple \\ (S = ( 2 ، 1 ، 1) \\)) ، سوف تبدو الرياضيات مثل هذا:
\[
تبدأ {} الانحياز
تبدأ {bmatrix}
{b_{0\\
{b_{1\\
{b_{2
\\ نهاية {} bmatrix
& =
تبدأ {bmatrix}
s0 & 0 & 0\\
0 & s1 & 0\\
0 & 0 & s2
\\ نهاية {} bmatrix
\\ تبدأ {bmatrix}
{a_{0\\
{a_{1\\
{a_{2
\\ نهاية {bmatrix} \\\\
& =
\\ تبدأ {bmatrix}
2 & 0 & 0\\
0 & 1 & 0\\
0 & 0 & 1
نهاية {} bmatrix
\\ تبدأ {bmatrix}
{A_ {0 \\\\
{a_{1\\
{a_{2
\\ نهاية {bmatrix} \\\\
& =
\\ تبدأ {bmatrix}
{2a_{0} + 0a_{1} + 0a_{2\\
0a_{0} + 1a_{1} + 0a_{2}\\
0a_{0} + 0a_{1} + 1a_{2}
\\ نهاية {bmatrix} \\\\
& =
\\ تبدأ {bmatrix}
2a_{0}\\
a_{1}\\
a_{2}
\\ نهاية {} bmatrix
\\ نهاية {} الانحياز
\]
لذلك إذا تم إعطاء متجه الإدخال \\ (A = (3،4،0) \\) ، فسيكون الناتج الخاص بك vector \\ (B \\) \\ ((6،4،0) \\).



التمرين: وظائف القياس
كممارسة أخرى ، قم بإضافة وظيفة جديدة إلى فئة المتجه الخاصة بك لقياسها. يجب أن تأخذ هذه الوظيفة الجديدة في 3-tuple التحجيم وإرجاع ناقل الإخراج.
يجب أن يكون التدفق الأساسي لوظائفك كما يلي:
- خلق ناقل الإخراج.
- حل لكل جزء من مجموعة متجهات الإخراج باستخدام المعادلة الموضحة أعلاه (والتي يمكن تبسيطها إلى
y0 = x0 * s0؛ y1 = x1 * s1؛ y2 = x2 * s2
). - إرجاع ناقل الإخراج.
دعونا نبني شيئا!
الآن بعد أن أصبحت لديك تحويلات خطية تحت الحزام ، دعنا نبني برنامجًا صغيرًا سريعًا لاكتشاف مهاراتك الجديدة. سنقوم بعمل برنامج يرسم مجموعة من النقاط على الشاشة ، ثم يسمح لنا بتعديلها ككل من خلال إجراء تحويلات خطية عليها.
قبل البدء ، سنحتاج أيضًا إلى إضافة وظيفة أخرى إلى فئة النقطة
. سيسمى هذا setPointToPoint ()
، وسيقوم ببساطة بتعيين موضع النقطة الحالية إلى النقطة التي يتم تمريرها إليه. سيحصل على نقطة كمدخل ، وسوف يعيد شيئا.
فيما يلي بعض المواصفات السريعة لبرنامجنا:
- سيحتوي البرنامج على 100 نقطة في صفيف.
- عند الضغط على المفتاح D ، سيقوم البرنامج بمسح الشاشة الحالية وإعادة رسم النقاط.
- عندما يتم الضغط على المفتاح A ، سيقوم البرنامج بقياس جميع مواقع النقاط بمقدار 0.5.
- عندما يتم الضغط على المفتاح S ، سيقوم البرنامج بقياس جميع مواقع النقاط بمقدار 2.0.
- عند الضغط على مفتاح R ، سيقوم البرنامج بتدوير كل موقع النقاط بمقدار 15 درجة على مستوى XY.
- عند الضغط على مفتاح Escape ، سيقوم البرنامج بالخروج (إلا إذا كنت تستخدمه مع JavaScript أو لغة أخرى موجهة للويب).
فصولنا الحالية:
1 |
Point Class |
2 |
{
|
3 |
Variables:
|
4 |
num tuple[3]; //(x,y,z) |
5 |
|
6 |
Operators:
|
7 |
Point AddVectorToPoint(Vector); |
8 |
Point SubtractVectorFromPoint(Vector); |
9 |
Vector SubtractPointFromPoint(Point); |
10 |
//sets the current point's position to that of the inputted point
|
11 |
Null SetPointToPoint(Point); |
12 |
Functions:
|
13 |
//draw a point at its position tuple with your favorite graphics API
|
14 |
drawPoint; |
15 |
}
|
16 |
|
17 |
Vector Class |
18 |
{
|
19 |
Variables:
|
20 |
num tuple[3]; //(x,y,z) |
21 |
|
22 |
Operators:
|
23 |
Vector AddVectorToVector(Vector); |
24 |
Vector SubtractVectorFromVector(Vector); |
25 |
Vector RotateXY(degrees); |
26 |
Vector RotateYZ(degrees); |
27 |
Vector RotateXZ(degrees); |
28 |
Vector Scale(s0,s1,s2); |
29 |
}
|
مع هذه المواصفات ، دعونا ننظر إلى ما يمكن أن تكون عليه الكود:
1 |
main{ |
2 |
//setup for your favorite Graphics API here
|
3 |
//setup for keyboard input (may not be required) here
|
4 |
|
5 |
//create an array of 100 points
|
6 |
Point Array pointArray[100]; |
7 |
|
8 |
for (int x = 0; x < pointArray.length; x++) |
9 |
{
|
10 |
//Set its location to a random point on the screen
|
11 |
pointArray[x].tuple = [random(0,screenWidth), random(0,screenHeight), random(0,desiredDepth)); |
12 |
}
|
13 |
|
14 |
//this function clears the screen and then draws all of the points
|
15 |
function redrawScreen() |
16 |
{
|
17 |
//use your Graphics API's clear screen function
|
18 |
ClearTheScreen(); |
19 |
|
20 |
for (int x = 0; x < pointArray.length; x++) |
21 |
{
|
22 |
//draw the current point to the screen
|
23 |
pointArray[x].drawPoint(); |
24 |
}
|
25 |
}
|
26 |
|
27 |
// while the escape is not being pressed, carry out the main loop
|
28 |
while (esc != pressed) |
29 |
{
|
30 |
// perform various actions based on which key is pressed
|
31 |
if (key('d') == pressed) |
32 |
{
|
33 |
redrawScreen(); |
34 |
}
|
35 |
if (key('a') == pressed) |
36 |
{
|
37 |
//create the space's origin as a point
|
38 |
Point origin = new Point(0,0,0); |
39 |
|
40 |
Vector tempVector; |
41 |
for (int x = 0; x < pointArray.length; x++) |
42 |
{
|
43 |
//store the current vector address for the point, and set the point
|
44 |
tempVector = pointArray[x].subtractPointFromPoint(origin); |
45 |
//reset the point so that the scaled vector can be added
|
46 |
pointArray[x].setPointToPoint(origin); |
47 |
//scale the vector and set the point to its new, scaled location
|
48 |
pointArray[x].addVectorToPoint(tempVector.scale(0.5,0.5,0.5)); |
49 |
}
|
50 |
redrawScreen(); |
51 |
}
|
52 |
|
53 |
if(key('s') == pressed) |
54 |
{
|
55 |
//create the space's origin as a point
|
56 |
Point origin = new Point(0,0,0); |
57 |
|
58 |
Vector tempVector; |
59 |
for (int x = 0; x < pointArray.length; x++) |
60 |
{
|
61 |
//store the current vector address for the point, and set the point
|
62 |
tempVector = pointArray[x].subtractPointFromPoint(origin); |
63 |
//reset the point so that the scaled vector can be added
|
64 |
pointArray[x].setPointToPoint(origin); |
65 |
//scale the vector and set the point to its new, scaled location
|
66 |
pointArray[x].addVectorToPoint(tempVector.scale(2.0,2.0,2.0)); |
67 |
}
|
68 |
redrawScreen(); |
69 |
}
|
70 |
|
71 |
if(key('r') == pressed) |
72 |
{
|
73 |
//create the space's origin as a point
|
74 |
Point origin = new Point(0,0,0); |
75 |
Vector tempVector; |
76 |
for (int x = 0; x < pointArray.length; x++) |
77 |
{
|
78 |
//store the current vector address for the point, and set the point
|
79 |
tempVector = pointArray[x].subtractPointFromPoint(origin); |
80 |
//reset the point so that the scaled vector can be added
|
81 |
pointArray[x].setPointToPoint(origin); |
82 |
//scale the vector and set the point to its new, scaled location
|
83 |
pointArray[x].addVectorToPoint(tempVector.rotateXY(15)); |
84 |
}
|
85 |
redrawScreen(); |
86 |
}
|
87 |
}
|
88 |
}
|
الآن يجب أن يكون لديك برنامج رائع ، صغير لإظهار جميع تقنياتك الجديدة! يمكنك التحقق من بلدي التجريبي بسيط هنا.
استنتاج
في حين أننا بالتأكيد لم نغطي كل تحول خطي ممكن ، فإن محركنا الصغير بدأ في التبلور.
كما هو الحال دائمًا ، هناك بعض الأشياء التي تم تركها خارج محركنا من أجل البساطة (بالتحديد والانعكاسات في هذا الجزء). إذا كنت ترغب في معرفة المزيد عن هذين النوعين من التحولات الخطية ، يمكنك معرفة المزيد عنها في ويكيبيديا والروابط ذات الصلة بها.
في الجزء التالي من هذه السلسلة ، سنغطي مساحات عرض مختلفة وكيفية إعدام كائنات خارج نطاق رؤيتنا.
إذا كنت بحاجة إلى مساعدة إضافية ، توجه إلى Envato Studio ، حيث يمكنك العثور على الكثير من خدمات التصميم والنمذجة ثلاثية الأبعاد الرائعة. يمكن لمقدمي الخدمات ذوي الخبرة هذه مساعدتك في مجموعة واسعة من المشاريع المختلفة ، لذلك فقط قم بتصفح مقدمي الخدمات ، وقراءة التعليقات والتقييمات ، واختيار الشخص المناسب لمساعدتك.


