Advertisement
  1. Game Development
  2. Programming

دعونا نبني محرك رسومات ثلاثية الأبعاد: المثلثات النقطية والرباعية

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

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

مرحبًا بك في الجزء الخامس من سلسلة Let Let's Build 3D Graphics Engine! في هذه المرة ، سنبني فصلين دراسيين جديدين للتنقيط: واحد للمثلثات والآخر للدرجات الرباعية الأساسية. بعد ذلك ، سنأخذ قطعًا من هاتين الفئتين ونجمع طبقة مضلعة نهائية وقوية.

نصيحة: هذا جزء من سلسلة ، لذلك إذا كنت ترغب في الحصول على أقصى استفادة منه ، فتأكد من قراءة البرامج التعليمية الأخرى التي سبقت هذا.


خلاصة

لقد قمنا ببناء بعض الشيء في محركنا حتى الآن! إليكم ما لدينا:

  • فاصلات النقطة و المتجهات (وحدات البناء لمحركنا).
  • وظائف التحول لنقاطنا.
  • فئة الكاميرا (تحدد إطار العرض الخاص بنا ، وتعطّل النقاط خارج الشاشة).
  • فئتان للتنقيط (شرائح الخط والدوائر).

في ما يلي مرجع سريع لجميع الفصول التي أنشأناها:

1
2
Point Class
3
{
4
  Variables:
5
		num tuple[3]; //(x,y,z)

6
	Operators:
7
		Point AddVectorToPoint(Vector);
8
		Point SubtractVectorFromPoint(Vector);
9
		Vector SubtractPointFromPoint(Point);
10
		Null SetPointToPoint(Point);
11
	Functions:
12
		drawPoint; //draw a point at its position tuple

13
}
14
15
Vector Class
16
{
17
	Variables:
18
		num tuple[3]; //(x,y,z)

19
	Operators:
20
		Vector AddVectorToVector(Vector);
21
		Vector SubtractVectorFromVector(Vector);
22
		Vector RotateXY(degrees);
23
		Vector RotateYZ(degrees);
24
		Vector RotateXZ(degrees);
25
		Vector Scale(s0,s1,s2); //receives a scaling 3-tuple, returns the scaled vector

26
}
27
28
Camera Class
29
{
30
	Vars:
31
		int minX, maxX;
32
		int minY, maxY;
33
		int minZ, maxZ;
34
		array objectsInWorld; //an array of all existent objects

35
	Functions:
36
		null drawScene(); //draws all needed objects to the screen

37
}
38
39
LineSegment Class
40
{
41
	Variables:
42
		int startX, startY; //the starting point of our line segment

43
		int endX, endY; //the ending point of our line segment

44
	Function:
45
		array returnPointsInSegment; //all points lying on this line segment

46
}

سوف نعتمد بشكل كبير على فئة LineSegment لإنشاء فئتي Triangle و Quad ، لذا تأكد من إعادة التعرف عليها قبل الانتقال.


المثلثات النقطية

512px-Euler_diagram_of_triangle_types512px-Euler_diagram_of_triangle_types512px-Euler_diagram_of_triangle_types

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

يمكن أن يظهر المخطط الأساسي للفصل كالتالي:

1
2
Triangle Class
3
{
4
	Variables:
5
		//co-ordinates for the three points of our triangles

6
		int Point1X, Point1Y; 
7
		int Point2X, Point2Y;
8
		int Point3X, Point3Y;
9
	Function:
10
		array returnPointsInTriangle; //all points within the triangle's perimeter

11
}

من أجل المعايير ، سنفترض أن النقاط الثلاث المعلنة ضمن مثلثنا هي في نمط عقارب الساعة.

باستخدام فئة LineSegment ، يمكننا إعداد وظيفة returnPointsInTriangle() الخاصة بنا على النحو التالي:

1
2
function returnPointsInTriangle()
3
{
4
	array PointsToReturn; //create a temporary array to hold the triangle's points

5
6
	//Create three line segments and store their points in the array

7
	PointsToReturn.push(new LineSegment(this.Point1X, this.Point1Y, this.Point2X, this.Point2Y));
8
	PointsToReturn.push(new LineSegment(this.Point2X, this.Point2Y, this.Point3X, this.Point3Y));
9
	PointsToReturn.push(new LineSegment(this.Point3X, this.Point3Y, this.Point1X, this.Point1Y));
10
11
	return(PointsToReturn);
12
}

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

بعد ذلك ، دعونا نلقي نظرة على كيفية إضافة المزيد من النقاط إلى هذا النظام من خلال إنشاء فئة مربعة.


الحصول على تربيعي بعيدا

300px-Six_Quadrilaterals

لا يتطلب وضع فئة للتعامل مع الأجيال الرباعية سوى إضافة بعض الأشياء الإضافية إلى صفنا المثلث. مع مجموعة أخرى من النقاط ، فإن صفنا الرباعي سيبدو هكذا:

1
2
Quad Class
3
{
4
	Variables:
5
		int Point1X, Point1Y; //co-ordinates for the four points of our quadrilateral

6
		int Point2X, Point2Y;
7
		int Point3X, Point3Y;
8
		int Point4X, Point4Y;
9
10
 	Function:
11
		 array returnPointsInQuad; //return all points within the quadrilateral

12
}

ثم ، نقوم فقط بإضافة جزء السطر الإضافي إلى الدالة returnPointsInQuad ، مثل:

1
2
function returnPointsInQuad()
3
{
4
	array PointsToReturn; //create a temporary array to hold the quad's points

5
6
	//Create four line segments and store their points in the array

7
	PointsToReturn.push(new LineSegment(this.Point1X, this.Point1Y, this.Point2X, this.Point2Y));
8
	PointsToReturn.push(new LineSegment(this.Point2X, this.Point2Y, this.Point3X, this.Point3Y));
9
	PointsToReturn.push(new LineSegment(this.Point3X, this.Point3Y, this.Point4X, this.Point4Y));
10
	PointsToReturn.push(new LineSegment(this.Point4X, this.Point4Y, this.Point1X, this.Point1Y));
11
12
	return(PointsToReturn);
13
}

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


أين لديك كل بوليس غون؟

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

1
2
Polygon Class
3
{
4
	Variables:
5
		array Points; //holds all of the polygon's points in an array

6
7
	Function:
8
		array returnPointsInPolygon; //an array holding all of the polygon's points

9
}

والثاني هو استخدام حلقة للسماح لعدد غير مسمّى من مقاطع الخطوط بالعبور في وظيفة returnPointsInPolygon() التي قد تبدو كالتالي:

1
2
function returnPointsInPolygon
3
{
4
	array PointsToReturn; //a temporary array to hold the polygon's points

5
6
	//loop through all points in the polygon, moving one co-ordinate pair at a time (by a step of two)

7
	for(int x = 0; x < this.Points.length; x+=2)
8
	{
9
		if(this is not the last point)
10
		{
11
			//create a line segment between this point and the next one in the array

12
			PointsToReturn.push(new LineSegment(this.Points[x], this.Points[x+1], this.Points[x+2], this.Points[x+3]));
13
		}
14
		else if(this is the last point)
15
		{
16
			//create a line segment between this point and the first point in the array

17
			PointsToReturn.push(new LineSegment(this.Points[x-2], this.Points[x-1], this.Points[0], this.Points[1]));
18
		}
19
	}
20
21
	//return the array of points

22
	return PointsToReturn;
23
}

مع إضافة هذه الفئة إلى محركنا ، يمكننا الآن إنشاء أي شيء من المثلث إلى بعض المراجعين من 39 جانبًا مع نفس سطر الشفرة.


مضلع مضلع

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

يمكن تقسيم مواصفات برنامجنا إلى هذه الأجزاء الصغيرة:

  • ارسم مضلعًا على الشاشة في البداية.
  • عند الضغط على المفتاح 'a' ، قم بتخفيض عدد الجوانب على المضلع بمقدار 1.
  • عندما يتم الضغط على مفتاح 's' ، قم بزيادة عدد الجوانب على المضلع بمقدار 1.
  • منع عدد مضلع الجانبين من السقوط تحت 3.
  • منع عدد مضلع الجوانب من الزيادة فوق 10.

لنلق نظرة على شكل الرمز الخاص بنا:

1
2
main{
3
	//setup for your favorite Graphics API here

4
	//setup for keyboard input (may not be required) here

5
6
	var camera = new Camera(); //create an instance of our camera class

7
	camera.objectsInWorld[]; //initialize the camera's object array

8
9
	//set up the camera's view space

10
	camera.minX = 0;
11
	camera.maxX = screenWidth;
12
	camera.minY = 0;
13
	camera.maxY = screenHeight;
14
	camera.minZ = 0;
15
	camera.maxZ = 100;
16
17
	//create an array of points for each polygon size

18
	var threeSides = new Array(100,100,100,50,50,50);
19
	var fourSides = new Array(points in here);
20
	var fiveSides = new Array(points in here);
21
	var sixSides = new Array(points in here);
22
	var sevenSides = new Array(points in here);
23
	var eightSides = new Array(points in here);
24
	var nineSides = new Array(points in here);
25
	var tenSides = new Array(points in here);
26
27
	//store all of the arrays in another array for easier access

28
	var sidesArray = new Array(threeSides, fourSides, fiveSides, sixSides, sevenSides, eightSides, nineSides, tenSides);
29
30
	//keep track of how many points the polygon currently has

31
	var polygonPoints = 3;
32
33
	//create the initial polygon to be displayed

34
	var polygon = new Polygon(sidesArray[0][0], sidesArray[0][1], sidesArray[0][2], sidesArray[0][3], sidesArray[0][4], sidesArray[0][5],);
35
	//draw the initial polygon to the screen

36
	camera.drawScene();
37
38
	//while the user has not pressed the escape key

39
	while(key != esc) {
40
		if(key pressed == 'a')
41
		{
42
			//if the polygon is not at risk of falling below 3

43
			if(polygonPoints != 3)
44
			{
45
				//reduce the number of points

46
				polygonPoints--;
47
				//change the polygon to have the correct number of points

48
			}
49
			//redraw the scene

50
			camera.drawScene();
51
		}
52
		else if(key pressed == 's')
53
		{
54
			//if the polygon is not at risk of going above 10

55
			if(polygonPoints != 10)
56
			{
57
				//increase the number of points

58
				polygonPoints++;
59
				//change the polygon to have the correct number of points

60
			}
61
			//redraw the scene

62
			camera.drawScene();
63
		}
64
	}
65
}

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


خاتمة

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

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.