Advertisement
  1. Game Development
  2. Programming

دعونا نبني محرك رسومات ثلاثية الأبعاد: Spaces and Culling

Scroll to top
Read Time: 7 min
This post is part of a series called Let’s Build a 3D Graphics Software Engine.
Let's Build a 3D Graphics Engine: Linear Transformations
Let's Build a 3D Graphics Engine: Rasterizing Line Segments and Circles

() translation by (you can also view the original English article)

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

نصيحة: إذا لم تكن قد قرأت الجزءين الأول والثاني بعد ، فأقترح بشدة أن تفعل ذلك قبل المتابعة.

يمكنك أيضًا الحصول على مساعدة إضافية على Envato Studio ، حيث يمكنك الاختيار من بين مجموعة واسعة من خدمات تصميم 3D عالية الجودة  من مزودي الخدمات ذوي الخبرة.

3D Design Modeling services3D Design Modeling services3D Design Modeling services
3D تصميم * خدمات النمذجة على Envato ستوديو

خلاصة

أولاً ، دعنا نلقي نظرة على الفصول الدراسية التي أنشأناها حتى الآن:

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
}

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

أولاً ، سوف نريد وظيفة يمكن استدعاؤها خارجياً من الفصل الذي سيجذب المشهد. تقوم هذه الوظيفة بالدوران عبر كل نقطة موجودة ، ومقارنتها بمعلمات اجتثاث الكاميرا ، ورسمها إذا كان ذلك ممكنًا.

ViewFrustumViewFrustumViewFrustum
المصدر: http://en.wikipedia.org/wiki/File:ViewFrustum.svg

نصيحة: إذا كنت تريد فصل نظام الكاميرا عن العارض الخاص بك ، يمكنك ببساطة إنشاء فئة برنامج 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
}

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


خاتمة

باستخدام الكاميرا ونظام التقديم تحت الحزام ، يمكنك القول تقنيًا إنك أنشأت محرك لعبة ثلاثي الأبعاد! قد لا تكون مثيرة للإعجاب بشكل مفرط ، لكنها في طريقها.

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

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Game Development tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.