مرحبا بالجميع! اسمي Grisha وأنا مؤسس CGDevs. اليوم أريد أن أستمر في موضوع الرياضيات في لعبة ديف. في
مقال سابق ، أظهرنا أمثلة أساسية لاستخدام المتجهات والتكاملات في مشاريع الوحدة ، والآن دعونا نتحدث عن المصفوفات وتحولات القرب. إذا كنت ضليعا في مصفوفة الحساب ؛ أنت تعرف ماهية TRS وكيفية التعامل معها ؛ ما هو تحول صاحب المنزل - ربما لن تجد شيئًا جديدًا لنفسك. سنتحدث في سياق الرسومات ثلاثية الأبعاد. إذا كنت مهتما بهذا الموضوع - مرحبا بكم في القط.

لنبدأ بواحد من أهم المفاهيم في سياق المقالة -
تحويلات الألفة .
إن تحولات الأفين هي ، في جوهرها ، تحول نظام الإحداثيات (أو الفضاء) عن طريق ضرب المتجه بمصفوفة خاصة. على سبيل المثال ، التحولات مثل الحركة ، الدوران ، التدرج ، الانعكاس ، إلخ. الخصائص الرئيسية للتحولات الذكرية هي أنك تبقى في نفس المكان (من المستحيل أن تصنع متجهًا ثنائي الأبعاد من ثنائي الأبعاد) وأنه إذا كانت الخطوط متقاطعة / متوازية / تم شطبها قبل التحويل ، ثم سيتم الاحتفاظ بهذه الخاصية بعد التحويل. بالإضافة إلى ذلك ، لديهم الكثير من الخصائص الرياضية التي تتطلب معرفة نظرية المجموعات والمجموعات والجبر الخطي ، مما يجعل العمل معهم أسهل.
مصفوفة TRS
المفهوم المهم الثاني في رسومات الحاسوب هو
مصفوفة TRS . باستخدامه ، يمكنك وصف العمليات الأكثر شيوعًا المستخدمة عند العمل باستخدام رسومات الكمبيوتر.
مصفوفة TRS هي تركيبة من ثلاث مصفوفات تحويل. مصفوفات الإزاحة (الترجمة) ، الدوران على كل محور (الدوران) والقياس (مقياس).
انها تبدو مثل هذا.

حيث:
الخطوة هي
t = Vector3 جديدة (د ، ح ، ل).
القياس -
s = new Vector3 (Vector3 (a ، e ، i) .magnitude ، Vector3 (b، f، j) .magnitude، Vector3 (c، g، k) .magnitude)؛
التناوب هو مصفوفة من الشكل:

الآن دعنا نذهب قليلاً في سياق الوحدة. بادئ ذي بدء ، مصفوفة TRS هي شيء مريح للغاية ، ولكن لا ينبغي استخدامها في كل مكان. نظرًا لأن الإشارة البسيطة لموضع أو إضافة المتجهات في الوحدة ستعمل بشكل أسرع ، ولكن في العديد من الخوارزميات الرياضية ، تكون المصفوفات أكثر ملاءمةً من المتجهات عدة مرات. يتم تطبيق وظيفة TRS في الوحدة إلى حد كبير في فئة
Matrix4x4 ، لكنها ليست ملائمة من وجهة نظر التطبيق. نظرًا لأنه ، بالإضافة إلى تطبيق المصفوفة من خلال الضرب ، يمكنه عمومًا تخزين المعلومات حول اتجاه الكائن ، وبالنسبة لبعض التحويلات ، أود أن أكون قادرًا على حساب ليس فقط الموضع ، ولكن أيضًا تغيير اتجاه الكائن ككل (على سبيل المثال ، الانعكاس الذي لا يتم تطبيقه في الوحدة)
يتم توفير جميع الأمثلة أدناه لنظام الإحداثيات المحلي (يُعتبر أصل GameObject هو أصل الإحداثيات ، حيث يقع الكائن داخلها. إذا كان الكائن هو جذر التسلسل الهرمي في الوحدة ، يكون الأصل هو العالم (0،0،0)).
نظرًا لاستخدام مصفوفة TRS ، يمكننا وصف موضع كائن ما في الفضاء ، نحتاج إلى تحلل من TRS إلى القيم المحددة للموضع ، الدوران ومقياس الوحدة. للقيام بذلك ، يمكنك كتابة طرق التمديد للفئة Matrix4x4
الحصول على الموقف ، الدوران والحجمpublic static Vector3 ExtractPosition(this Matrix4x4 matrix) { Vector3 position; position.x = matrix.m03; position.y = matrix.m13; position.z = matrix.m23; return position; } public static Quaternion ExtractRotation(this Matrix4x4 matrix) { Vector3 forward; forward.x = matrix.m02; forward.y = matrix.m12; forward.z = matrix.m22; Vector3 upwards; upwards.x = matrix.m01; upwards.y = matrix.m11; upwards.z = matrix.m21; return Quaternion.LookRotation(forward, upwards); } public static Vector3 ExtractScale(this Matrix4x4 matrix) { Vector3 scale; scale.x = new Vector4(matrix.m00, matrix.m10, matrix.m20, matrix.m30).magnitude; scale.y = new Vector4(matrix.m01, matrix.m11, matrix.m21, matrix.m31).magnitude; scale.z = new Vector4(matrix.m02, matrix.m12, matrix.m22, matrix.m32).magnitude; return scale; }
بالإضافة إلى ذلك ، من أجل العمل المريح ، يمكنك تنفيذ بعض ملحقات فئة التحويل للعمل مع TRS فيه.
تحويل التوسع public static void ApplyLocalTRS(this Transform tr, Matrix4x4 trs) { tr.localPosition = trs.ExtractPosition(); tr.localRotation = trs.ExtractRotation(); tr.localScale = trs.ExtractScale(); } public static Matrix4x4 ExtractLocalTRS(this Transform tr) { return Matrix4x4.TRS(tr.localPosition, tr.localRotation, tr.localScale); }
على هذا ، فإن مزايا الوحدة تنتهي ، حيث أن المصفوفات في الوحدة ضعيفة جدًا في العملية. تتطلب العديد من الخوارزميات حساب مصفوفة ، والذي لا يتم تنفيذه في الوحدة حتى في العمليات الأساسية تمامًا ، مثل إضافة المصفوفات ومصفوفات الضرب بواسطة العددية. بالإضافة إلى ذلك ، نظرًا لخصوصية تنفيذ المتجهات في Unity3d ، هناك أيضًا عدد من الإزعاج المرتبط بحقيقة أنه يمكنك إنشاء ناقل 4 × 4 ، ولكن لا يمكنك إخراج 1 × 4 من المربع. نظرًا لأننا سنتحدث أكثر عن تحول "صاحب المنزل" للانعكاسات ، فإننا ننفذ أولاً العمليات اللازمة لذلك.
تعد عملية إضافة / طرح وضرب عددية بسيطة. يبدو ضخمًا إلى حد ما ، لكن لا يوجد شيء معقد هنا ، لأن الحساب بسيط.
عمليات المصفوفة الأساسية public static Matrix4x4 MutiplyByNumber(this Matrix4x4 matrix, float number) { return new Matrix4x4( new Vector4(matrix.m00 * number, matrix.m10 * number, matrix.m20 * number, matrix.m30 * number), new Vector4(matrix.m01 * number, matrix.m11 * number, matrix.m21 * number, matrix.m31 * number), new Vector4(matrix.m02 * number, matrix.m12 * number, matrix.m22 * number, matrix.m32 * number), new Vector4(matrix.m03 * number, matrix.m13 * number, matrix.m23 * number, matrix.m33 * number) ); } public static Matrix4x4 DivideByNumber(this Matrix4x4 matrix, float number) { return new Matrix4x4( new Vector4(matrix.m00 / number, matrix.m10 / number, matrix.m20 / number, matrix.m30 / number), new Vector4(matrix.m01 / number, matrix.m11 / number, matrix.m21 / number, matrix.m31 / number), new Vector4(matrix.m02 / number, matrix.m12 / number, matrix.m22 / number, matrix.m32 / number), new Vector4(matrix.m03 / number, matrix.m13 / number, matrix.m23 / number, matrix.m33 / number) ); } public static Matrix4x4 Plus(this Matrix4x4 matrix, Matrix4x4 matrixToAdding) { return new Matrix4x4( new Vector4(matrix.m00 + matrixToAdding.m00, matrix.m10 + matrixToAdding.m10, matrix.m20 + matrixToAdding.m20, matrix.m30 + matrixToAdding.m30), new Vector4(matrix.m01 + matrixToAdding.m01, matrix.m11 + matrixToAdding.m11, matrix.m21 + matrixToAdding.m21, matrix.m31 + matrixToAdding.m31), new Vector4(matrix.m02 + matrixToAdding.m02, matrix.m12 + matrixToAdding.m12, matrix.m22 + matrixToAdding.m22, matrix.m32 + matrixToAdding.m32), new Vector4(matrix.m03 + matrixToAdding.m03, matrix.m13 + matrixToAdding.m13, matrix.m23 + matrixToAdding.m23, matrix.m33 + matrixToAdding.m33) ); } public static Matrix4x4 Minus(this Matrix4x4 matrix, Matrix4x4 matrixToMinus) { return new Matrix4x4( new Vector4(matrix.m00 - matrixToMinus.m00, matrix.m10 - matrixToMinus.m10, matrix.m20 - matrixToMinus.m20, matrix.m30 - matrixToMinus.m30), new Vector4(matrix.m01 - matrixToMinus.m01, matrix.m11 - matrixToMinus.m11, matrix.m21 - matrixToMinus.m21, matrix.m31 - matrixToMinus.m31), new Vector4(matrix.m02 - matrixToMinus.m02, matrix.m12 - matrixToMinus.m12, matrix.m22 - matrixToMinus.m22, matrix.m32 - matrixToMinus.m32), new Vector4(matrix.m03 - matrixToMinus.m03, matrix.m13 - matrixToMinus.m13, matrix.m23 - matrixToMinus.m23, matrix.m33 - matrixToMinus.m33) ); }
ولكن للتفكير ، نحن بحاجة إلى تشغيل ضرب المصفوفة في حالة معينة. ضرب متجه البعد 4 × 4 × 1 × 4 (تم نقله) إذا كنت معتادًا على مصفوفة الرياضيات ، فأنت تعلم أنه مع هذا الضرب ، ستحتاج إلى النظر إلى الأعداد القصوى للبعد ، وستحصل على البعد من المصفوفة في المخرجات ، أي في هذه الحالة 4x4. هناك معلومات كافية حول كيفية مضاعفة المصفوفات ، لذلك لن نرسم هذا. فيما يلي مثال لحالة معينة ستكون مفيدة في المستقبل.
اضرب المتجه بواسطة المنقول public static Matrix4x4 MultiplyVectorsTransposed(Vector4 vector, Vector4 transposeVector) { float[] vectorPoints = new[] {vector.x, vector.y, vector.z, vector.w}, transposedVectorPoints = new[] {transposeVector.x, transposeVector.y, transposeVector.z, transposeVector.w}; int matrixDimension = vectorPoints.Length; float[] values = new float[matrixDimension * matrixDimension]; for (int i = 0; i < matrixDimension; i++) { for (int j = 0; j < matrixDimension; j++) { values[i + j * matrixDimension] = vectorPoints[i] * transposedVectorPoints[j]; } } return new Matrix4x4( new Vector4(values[0], values[1], values[2], values[3]), new Vector4(values[4], values[5], values[6], values[7]), new Vector4(values[8], values[9], values[10], values[11]), new Vector4(values[12], values[13], values[14], values[15]) ); }
تحول صاحب المنزل
بحثًا عن كيفية عكس كائن نسبة إلى أي محور ، غالبًا ما صادفت نصيحة لوضع مقياس سلبي في الاتجاه الضروري. هذه نصيحة سيئة للغاية في سياق الوحدة ، لأنها تكسر الكثير من الأنظمة في المحرك (الذبح ، الاصطدام ، إلخ.) في بعض الخوارزميات ، يتحول هذا إلى حسابات غير تافهة تمامًا إذا كنت بحاجة إلى التفكير بشكل تافه فيما يتعلق بـ Vector3.up أو Vector3.forward ، ولكن في اتجاه تعسفي. لم يتم تنفيذ طريقة الانعكاس في وحدة خارج الصندوق نفسه ، لذلك قمت بتطبيق
طريقة Householder .
لا يستخدم
تحويل Householder في رسومات الكمبيوتر فحسب ، بل في هذا السياق هو تحول خطي يعكس كائنًا متعلقًا بمستوى يمر عبر "الأصل" ويتم تحديده بواسطة المستوى العادي إلى المستوى. في العديد من المصادر ، يتم وصفه بطريقة معقدة وغير مفهومة ، على الرغم من أن صيغته أولية.
H = I-2 * n * (n ^ T)حيث
H هي مصفوفة التحويل ، في حالتنا هي Matrix4x4.identity ، و
n = Vector4 جديدة (planeNormal.x ، planeNormal.y ، planeNormal.z ، 0). يعني الرمز T تبديل ، أي بعد ضرب
n * (n ^ T) نحصل على مصفوفة 4 × 4.
ستكون الأساليب المستخدمة سهلة الاستخدام وسيكون التسجيل مضغوطًا للغاية.
تحول صاحب المنزل public static Matrix4x4 HouseholderReflection(this Matrix4x4 matrix4X4, Vector3 planeNormal) { planeNormal.Normalize(); Vector4 planeNormal4 = new Vector4(planeNormal.x, planeNormal.y, planeNormal.z, 0); Matrix4x4 householderMatrix = Matrix4x4.identity.Minus( MultiplyVectorsTransposed(planeNormal4, planeNormal4).MutiplyByNumber(2)); return householderMatrix * matrix4X4; }
هام: المستوى الطبيعي يجب أن يكون طبيعيًا (وهو منطقي) ، والإحداثي الأخير n يجب أن يكون 0 ، بحيث لا يكون هناك أي تأثير للامتداد في الاتجاه ، لأنه يعتمد على طول المتجه n.
الآن للراحة في الوحدة نحن نطبق طريقة التمديد للتحول
انعكاس التحويل في نظام الإحداثيات المحلي public static void LocalReflect(this Transform tr, Vector3 planeNormal) { var trs = tr.ExtractLocalTRS(); var reflected = trs.HouseholderReflection(planeNormal); tr.ApplyLocalTRS(reflected); }
هذا كل شيء لهذا اليوم ، إذا كانت هذه السلسلة من المقالات ستظل مثيرة للاهتمام ، فسأكشف أيضًا عن تطبيقات الرياضيات الأخرى في تطوير اللعبة. هذه المرة لن يكون هناك أي مشروع ، حيث يتم وضع جميع الكود في المقال ، ولكن المشروع مع تطبيق معين سيكون في المقال التالي. من الصورة يمكنك تخمين ماذا ستكون المقالة القادمة.

شكرا لاهتمامكم!