دعونا نبني محرك رسومات ثلاثية الأبعاد: Spaces and Culling
() translation by (you can also view the original English article)
أهلا بك! هذا هو الجزء الثالث من سلسلتنا على محركات الرسومات ثلاثية الأبعاد. إذا كنت قد وصلت إلى هذا الحد في المسلسل ، فسوف يسعدنا أن تعرف أن هذه القطعة ستكون أخف بكثير على الجانب الرياضي لمحركات ثلاثية الأبعاد ، وبدلاً من ذلك سوف تركز على أشياء أكثر عملية - على وجه الخصوص ، إضافة كاميرا و نظام التقديم الأساسي.
نصيحة: إذا لم تكن قد قرأت الجزءين الأول والثاني بعد ، فأقترح بشدة أن تفعل ذلك قبل المتابعة.
يمكنك أيضًا الحصول على مساعدة إضافية على Envato Studio ، حيث يمكنك الاختيار من بين مجموعة واسعة من خدمات تصميم 3D عالية الجودة من مزودي الخدمات ذوي الخبرة.



خلاصة
أولاً ، دعنا نلقي نظرة على الفصول الدراسية التي أنشأناها حتى الآن:
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 |
Null SetPointToPoint(Point); // move point to specified point |
11 |
|
12 |
Functions:
|
13 |
drawPoint; //draw a point at its position tuple |
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 |
Vector RotateXY(degrees); |
25 |
Vector RotateYZ(degrees); |
26 |
Vector RotateXZ(degrees); |
27 |
Vector Scale(s0,s1,s2); //params: scaling along each axis |
28 |
}
|
لقد أثبت استخدام هاتين الفئتين من تلقاء نفسها قليلاً من الفوضى حتى الآن ، ويمكن أن يؤدي سحب كل نقطة ممكنة إلى استنزاف ذاكرة النظام بسرعة. لحل هذه المشاكل ، سوف نقدم فئة جديدة إلى محرك ألعابنا: الكاميرا.
ستكون كاميرتنا هي المكان الذي يحدث فيه كل عرضنا ، حصريًا ؛ ستقوم بإزالة جميع كائناتنا إلى الشاشة ، وستقوم أيضًا بإدارة قائمة بجميع نقاطنا.
ولكن قبل أن نتمكن من الوصول إلى كل ذلك ، يجب أن نتحدث أولاً قليلاً عن الإعدام.
لندن Culling
التعليب ، بحكم التعريف ، هو اختيار الكائنات من مجموعة أكبر من الكائنات. بالنسبة لمحرك ألعابنا ، سيكون التحديد الصغير الذي نأخذه هو النقاط التي نريد رسمها على الشاشة. المجموعة الأكبر من الكائنات ستكون كل نقطة موجودة.
إن القيام بذلك يقلل بشكل كبير من استنزاف محركك على ذاكرة النظام ، وذلك برسم ما يمكن للاعب فعلاً رؤيته ، بدلاً من نقطة كاملة في العالم. في محركنا ، سنقوم بذلك عن طريق تعيين المعلمات لمساحة العرض.
سيتم تحديد مساحة العرض الخاصة بنا عبر جميع المحاور الثلاثة التقليدية: x و y و z. سيتكون تعريف x من كل شيء بين حدود النافذة اليسرى واليمنى ، وسيتضمن تعريفها y كل شيء بين أعلى حدود النافذة والسفلية ، وسيكون تعريف z الخاص بها بين 0
(حيث تم تعيين الكاميرا) ومساحة العرض الخاصة باللاعب (من أجل عرضنا ، سنستخدم قيمة تعسفية قدرها 100
).
قبل رسم نقطة ، ستتحقق فئة الكاميرا لدينا لمعرفة ما إذا كانت هذه النقطة تكمن في مساحة العرض لدينا. إذا حدث ذلك ، عندها سيتم رسم النقطة ؛ خلاف ذلك ، لن.
يمكننا الحصول على بعض الكاميرات هنا؟
مع هذا الفهم الأساسي للإعدام ، يمكننا أن نستنتج أن صفنا سيبدو هكذا ، حتى الآن:
1 |
Camera Class |
2 |
{
|
3 |
Vars:
|
4 |
int minX, maxX; //minimum and maximum bounds of X |
5 |
int minY, maxY; //minimum and maximum bounds of Y |
6 |
int minZ, maxZ; //minimum and maximum bounds of Z |
7 |
}
|
سنقوم أيضاً بالتعامل مع الكاميرا الخاصة بنا لتقديم كل ما تحتاجه لمحركنا كذلك. بناءً على المحرك ، ستجد أن أجهزة العرض غالبًا ما تكون منفصلة عن أنظمة الكاميرا. ويتم ذلك عادة للحفاظ على النظام مغلفًا بشكل جيد ، حيث أنه - اعتمادًا على نطاق محركك - يمكن أن يتعرّض الاثنان إلى حالة من الفوضى إذا تم احتواؤها معًا. ولكن لأغراضنا ، سيكون من الأسهل التعامل معها على أنها واحدة.
أولاً ، سوف نريد وظيفة يمكن استدعاؤها خارجياً من الفصل الذي سيجذب المشهد. تقوم هذه الوظيفة بالدوران عبر كل نقطة موجودة ، ومقارنتها بمعلمات اجتثاث الكاميرا ، ورسمها إذا كان ذلك ممكنًا.



نصيحة: إذا كنت تريد فصل نظام الكاميرا عن العارض الخاص بك ، يمكنك ببساطة إنشاء فئة برنامج Renderer
، أو أن يقوم نظام الكاميرا بإعدام النقاط ، وتخزينها ليتم رسمها في صفيف ، ثم إرسال ذلك الصفيف إلى السحب ()
وظيفة العارض الخاص بك.
إدارة النقاط
الجزء الأخير من فئة الكاميرا سيكون نظام إدارة النقاط الخاص بها. اعتمادًا على لغة البرمجة التي تستخدمها ، يمكن أن يكون هذا مجرد مجموعة بسيطة من جميع الكائنات التي يمكن رسمها (سنقوم بمعالجة أكثر من مجرد نقاط في الأجزاء اللاحقة). بدلاً من ذلك ، قد يلزم الأمر استخدام فئة أصل الكائن الافتراضية. إذا كنت محظوظًا للغاية ، فسيتعين عليك إنشاء فئة أصل الكائن الخاص بك وجعل كل فصل دراسي (حتى الآن النقاط فقط) يكون طفلاً من هذا الصف.
بعد إضافة ذلك إلى الفصل ، ستبدو نظرة عامة أساسية عن الكاميرا كما يلي:
1 |
Camera Class |
2 |
{
|
3 |
Vars:
|
4 |
int minX, maxX; //minimum and maximum bounds of X |
5 |
int minY, maxY; //minimum and maximum bounds of Y |
6 |
int minZ, maxZ; //minimum and maximum bounds of Z |
7 |
array objectsInWorld; //an array of all existent objects |
8 |
Functions:
|
9 |
null drawScene(); //draws all needed objects to the screen, does not return anything |
10 |
}
|
مع هذه الإضافات ، دعونا نحسن قليلاً على البرنامج الذي قمنا به في المرة السابقة.
أكبر وأفضل الأشياء
سنقوم بإنشاء برنامج بسيط لرسم النقاط ، مع نموذج البرنامج الذي أنشأناه في المرة السابقة كنقطة بداية.
في هذا التكرار للبرنامج ، سنضيف في استخدام فئة الكاميرا الجديدة. عند الضغط على المفتاح D ، سيقوم البرنامج بإعادة رسم الشاشة بدون الاستبعاد ، وعرض عدد الكائنات التي تم عرضها في الزاوية العلوية اليمنى من الشاشة. عند الضغط على المفتاح C ، سيقوم البرنامج بإعادة رسم الشاشة بإعدامها ، وكذلك عرض عدد الكائنات المقدمة.
دعونا نلقي نظرة على الكود:
1 |
main{ |
2 |
//setup for your favorite Graphics API here
|
3 |
//setup for keyboard input (may not be required) here
|
4 |
|
5 |
var camera = new Camera(); //create an instance of the camera class |
6 |
camera.objectsInWorld[100]; //create 100 object spaces within the camera's array |
7 |
|
8 |
//set the camera's view space
|
9 |
camera.minX = 0; |
10 |
camera.maxX = screenWidth; |
11 |
camera.minY = 0; |
12 |
camera.maxY = screenHeight; |
13 |
camera.minZ = 0; |
14 |
camera.maxZ = 100; |
15 |
|
16 |
for(int x = 0; x < camera.objectsInWorld.length; x++) |
17 |
{
|
18 |
//Set its location to a random point on the screen
|
19 |
camera.objectsInWorld[x].tuple = [random(-200,1000), random(-200,1000), random(-100,200)); |
20 |
}
|
21 |
|
22 |
function redrawScreenWithoutCulling() //this function clears the screen and then draws all of the points |
23 |
{
|
24 |
ClearTheScreen(); //use your Graphics API's clear screen function |
25 |
for(int x = 0; x < camera.objectsInWorld.length; x++) |
26 |
{
|
27 |
camera.objectsInWorld[x].drawPoint(); //draw the current point to the screen |
28 |
}
|
29 |
}
|
30 |
|
31 |
while(esc != pressed) // the main loop |
32 |
{
|
33 |
if(key('d') == pressed) |
34 |
{
|
35 |
redrawScreenWithoutCulling(); |
36 |
}
|
37 |
if(key('c') == pressed) |
38 |
{
|
39 |
camera.drawScene(); |
40 |
}
|
41 |
if(key('a') == pressed) |
42 |
{
|
43 |
Point origin = new Point(0,0,0); |
44 |
Vector tempVector; |
45 |
for(int x = 0; x < camera.objectsInWorld.length; x++) |
46 |
{
|
47 |
//store the current vector address for the point, and set the point
|
48 |
tempVector = camera.objectsInWorld[x].subtractPointFromPoint(origin); |
49 |
//reset the point so that the scaled vector can be added
|
50 |
camera.objectsInWorld[x].setPointToPoint(origin); |
51 |
//scale the vector and set the point to its new, scaled location
|
52 |
camera.objectsInWorld[x].addVectorToPoint(tempVector.scale(0.5,0.5,0.5)); |
53 |
}
|
54 |
}
|
55 |
if(key('s') == pressed) |
56 |
{
|
57 |
Point origin = new Point(0,0,0); //create the space's origin as a point |
58 |
Vector tempVector; |
59 |
for(int x = 0; x < camera.objectsInWorld.length; x++) |
60 |
{
|
61 |
//store the current vector address for the point, and set the point
|
62 |
tempVector = camera.objectsInWorld[x].subtractPointFromPoint(origin); |
63 |
//reset the point so that the scaled vector can be added
|
64 |
camera.objectsInWorld[x].setPointToPoint(origin); |
65 |
//scale the vector and set the point to its new, scaled location
|
66 |
camera.objectsInWorld[x].addVectorToPoint(tempVector.scale(2.0,2.0,2.0)); |
67 |
}
|
68 |
}
|
69 |
if(key('r') == pressed) |
70 |
{
|
71 |
Point origin = new Point(0,0,0); //create the space's origin as a point |
72 |
Vector tempVector; |
73 |
for(int x = 0; x < camera.objectsInWorld.length; x++) |
74 |
{
|
75 |
//store the current vector address for the point, and set the point
|
76 |
tempVector = camera.objectsInWorld[x].subtractPointFromPoint(origin); |
77 |
//reset the point so that the scaled vector can be added
|
78 |
camera.objectsInWorld[x].setPointToPoint(origin); |
79 |
//scale the vector and set the point to its new, scaled location
|
80 |
camera.objectsInWorld[x].addVectorToPoint(tempVector.rotateXY(15)); |
81 |
}
|
82 |
}
|
83 |
}
|
84 |
}
|
الآن يمكنك أن ترى ، مباشرة ، سلطة اعدام! لاحظ أنه إذا كنت تبحث في نموذج التعليمة البرمجية ، فإن بعض الأشياء تتم بشكل مختلف قليلاً لجعل العروض التوضيحية أكثر ملائمة للويب. (يمكنك التحقق من بلدي التجريبي بسيط هنا.)
خاتمة
باستخدام الكاميرا ونظام التقديم تحت الحزام ، يمكنك القول تقنيًا إنك أنشأت محرك لعبة ثلاثي الأبعاد! قد لا تكون مثيرة للإعجاب بشكل مفرط ، لكنها في طريقها.
في مقالتنا التالية ، سننظر في إضافة بعض الأشكال الهندسية إلى محركنا (أي أجزاء الخط والدوائر) ، وسنتحدث عن الخوارزميات التي يمكن استخدامها لتناسب معادلاتها بوحدات البكسل في الشاشة.