ويب جي إل في تايب سكريبت

ويب جي إل في تايب سكريبت

تشرح هذه المقالة ويب جي إل باستخدام تايب سكريبت۔

سنقدم مفهوم ويب جي إل، وإعداداته الدنيا، وعملية الرسم، والتوسعات، وتصميم الفئات خطوة بخطوة مع أمثلة على التعليمات البرمجية۔

YouTube Video

ويب جي إل في تايب سكريبت

ويب جي إل هو واجهة برمجة تطبيقات منخفضة المستوى تتيح لك التحكم المباشر في وحدة معالجة الرسومات (GPU) ضمن المتصفح۔

من خلال استخدام TypeScript، يمكنك تقليل 'أخطاء التنفيذ' في WebGL بشكل كبير من خلال أمان الأنواع، واكتمال الكود، والبرمجة المهيكلة۔

تايب سكريبت فعال بشكل خاص في النقاط التالية:۔

  • تصبح أنواع كائنات ويب جي إل واضحة۔
  • يمكن تقليل الأخطاء في التعامل مع متغيرات الشادر۔
  • يصبح تتبع البنية أسهل۔

التهيئة الدنيا لـ ويب جي إل

فيما يلي الحد الأدنى من المتطلبات للرسم باستخدام ويب جي إل:۔

  • عنصر <canvas>
  • سياق WebGL
  • شادر الرؤوس (Vertex Shader)
  • شادر القطع (Fragment Shader)

أولاً، دعونا ننشئ حالة يمكن فيها تهيئة الشاشة۔

الحصول على عنصر كانفاس وسياق ويب جي إل

أولاً، احصل على عنصر canvas و WebGLRenderingContext۔

هنا، نستخدم كتابة تايب سكريبت مع التركيز على أمان القيم الفارغة۔

 1const canvas = document.getElementById('gl') as HTMLCanvasElement | null;
 2
 3if (!canvas) {
 4	throw new Error('Canvas not found');
 5}
 6
 7const gl = canvas.getContext('webgl');
 8
 9if (!gl) {
10	throw new Error('WebGL not supported');
11}
  • في هذه المرحلة، يصبح gl نقطة البداية لجميع عمليات ويب جي إل۔

مسح الشاشة للتأكد من أن ويب جي إل يعمل بشكل صحيح

قبل عملية الرسم، تحقق مما إذا كان يمكن تعبئة لون الخلفية۔

هذا هو الفحص الأولي للتأكد من أن ويب جي إل يعمل بشكل صحيح۔

1gl.clearColor(0.1, 0.1, 0.1, 1.0);
2gl.clear(gl.COLOR_BUFFER_BIT);
  • حتى هذه المرحلة، سيتم تعبئة الكانفاس بلون رمادي غامق۔

ما هو الشادر؟

في ويب جي إل، يتم وصف عمليات الرسم باستخدام لغة خاصة تسمى GLSL۔ في GLSL، تُحضِّر بشكل أساسي نوعين من الشيدرات: شيدر الرأس (vertex shaders) و شيدر العنصر (fragment shaders)۔

أولاً، سننشئ 'شادر عملي بسيط' باستخدام هذين الشادرين۔

كتابة شادر الرؤوس

شادر الرؤوس (Vertex shaders) يحدد مواقع النقاط أو الأشكال التي سيتم رسمها۔

فيما يلي كود بسيط يضع نقطة واحدة في منتصف الشاشة۔

1const vertexShaderSource = `
2attribute vec2 a_position;
3
4void main() {
5	gl_Position = vec4(a_position, 0.0, 1.0);
6}
7`;
  • a_position هو الإحداثي الذي يتم تمريره من جانب جافا سكريبت۔

كتابة شادر القطع

شادر القطع (Fragment shaders) يحدد الألوان التي تظهر على الشاشة۔

هذه المرة، يقوم دائمًا بإخراج اللون الأحمر۔

1const fragmentShaderSource = `
2precision mediump float;
3
4void main() {
5	gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
6}
7`;
  • يجب دائمًا تحديد precision في ويب جي إل۔

إنشاء دالة لترجمة الشادر

نظرًا لأن عملية إنشاء الشادر دائمًا متشابهة، سنحولها إلى دالة۔

 1function createShader(
 2	gl: WebGLRenderingContext,
 3	type: number,
 4	source: string
 5): WebGLShader {
 6	const shader = gl.createShader(type);
 7	if (!shader) {
 8		throw new Error('Failed to create shader');
 9	}
10
11	gl.shaderSource(shader, source);
12	gl.compileShader(shader);
13
14	if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
15		throw new Error(gl.getShaderInfoLog(shader) ?? 'Shader compile error');
16	}
17
18	return shader;
19}
  • تنشئ هذه الدالة شادر من النوع المحدد، وتترجم الكود المصدري، وتعرض خطأً إذا فشلت، وتعيد الشادر المترجم إذا نجحت العملية۔

إنشاء برنامج (مجموعة من الشادر)

اجمع شادر الرؤوس و شادر القطع في واحد۔

يسمى هذا برنامج ويب جي إل (WebGLProgram)۔

 1const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource);
 2const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource);
 3
 4const program = gl.createProgram();
 5if (!program) {
 6	throw new Error('Failed to create program');
 7}
 8
 9gl.attachShader(program, vertexShader);
10gl.attachShader(program, fragmentShader);
11gl.linkProgram(program);
12
13if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
14	throw new Error(gl.getProgramInfoLog(program) ?? 'Program link error');
15}
  • يربط هذا الكود شادر الرؤوس وشادر القطع في برنامج واحد ويتحقق مما إذا كان من الممكن استخدامه للرسم۔

تحضير بيانات الرؤوس

هنا، سنجهز بيانات الرؤوس لرسم مثلث۔

1// Vertex positions for a triangle in clip space (x, y)
2// Coordinates range from -1.0 to 1.0
3const vertices = new Float32Array([
4	0.0,  0.5,   // Top vertex
5	-0.5, -0.5,  // Bottom-left vertex
6	0.5, -0.5,   // Bottom-right vertex
7]);
  • نظام الإحداثيات في ويب جي إل يتراوح من -1.0 إلى 1.0۔

إنشاء مخزن مؤقت (Buffer) ونقله إلى وحدة معالجة الرسومات

بعد ذلك، لجعل مصفوفة بيانات الرؤوس قابلة للاستخدام من قبل الـGPU، نقوم بنقل البيانات باستخدام المخزن المؤقت (buffer)۔

1const buffer = gl.createBuffer();
2if (!buffer) {
3	throw new Error('Failed to create buffer');
4}
5
6gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
7gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
  • ينشئ هذا الكود مخزنًا مؤقتًا لتخزين بيانات الرؤوس وينقل المحتوى إلى وحدة معالجة الرسومات۔

ربط السمة (Attribute) بالمخزن المؤقت

قم بربط a_position في الشادر مع المخزن المؤقت الذي أنشئته مسبقًا۔

 1const positionLocation = gl.getAttribLocation(program, 'a_position');
 2
 3gl.enableVertexAttribArray(positionLocation);
 4gl.vertexAttribPointer(
 5	positionLocation,
 6	2,
 7	gl.FLOAT,
 8	false,
 9	0,
10	0
11);

الرسم

أخيرًا، استخدم البرنامج لإصدار أمر الرسم۔

1gl.useProgram(program);
2gl.drawArrays(gl.TRIANGLES, 0, 3);
  • عند تشغيل هذا البرنامج، سيتم عرض مثلث أحمر۔

تصميم الفئة (الكلاس) مع ويب جي إل

ويب جي إل هو واجهة برمجة تطبيقات أمرية، لذا يمكن أن يتضخم الكود بسرعة إذا كُتب كما هو۔ من خلال تصميم الفئات يمكنك الفصل الواضح بين التهيئة، والرسم، وإدارة الموارد۔

هنا، سنستفيد من مزايا تايب سكريبت ونطور التصميم تدريجيًا للتركيز على فصل المهام، وإمكانية إعادة الاستخدام، وسهولة الصيانة۔

سياسة التصميم (بسيطة ولكن عملية)

يتم تقسيم الصف إلى الأدوار التالية۔

  • GLApp يتحكم في التهيئة العامة وعملية الرسم۔
  • ShaderProgram يدير الشادر۔
  • TriangleMesh يدير بيانات الرؤوس۔

أولاً، لننشئ الفئة (الكلاس) التي تتحكم في كل شيء۔

فئة البداية (Entry class) لتطبيق ويب جي إل

GLApp يدير عنصر الكانفاس وسياق ويب جي إل ويكون نقطة البداية لعملية الرسم۔

 1export class GLApp {
 2	private gl: WebGLRenderingContext;
 3	private shader!: ShaderProgram;
 4	private mesh!: TriangleMesh;
 5
 6	constructor(private canvas: HTMLCanvasElement) {
 7		const gl = canvas.getContext('webgl');
 8		if (!gl) {
 9			throw new Error('WebGL not supported');
10		}
11		this.gl = gl;
12	}
13
14	initialize() {
15		this.gl.clearColor(0.1, 0.1, 0.1, 1.0);
16
17		this.shader = new ShaderProgram(this.gl);
18		this.mesh = new TriangleMesh(this.gl);
19	}
20
21	render() {
22		this.gl.clear(this.gl.COLOR_BUFFER_BIT);
23
24		this.shader.use();
25		this.mesh.draw(this.shader);
26	}
27}
  • تدير هذه الفئة عمليات تهيئة ويب جي إل والرسم، وهي مسؤولة عن عرض الرسومات على الشاشة باستخدام الشادر والأشكال الهندسية۔
  • من خلال فصل دالتي initialize و render، يصبح من الأسهل دعم إعادة التهيئة والرسوم المتحركة مستقبلاً۔

فئة لإدارة الشادر

بعد ذلك، أنشئ فئة لإدارة الشيدرات، لتجميع إنشاء الشيدرات وربطها واستخدامها في مكان واحد۔ وهذا يسمح لجانب الرسم بالتركيز فقط على 'استخدامها'۔

 1export class ShaderProgram {
 2	private program: WebGLProgram;
 3
 4	constructor(private gl: WebGLRenderingContext) {
 5		const vertexSource = `
 6		attribute vec2 a_position;
 7		void main() {
 8			gl_Position = vec4(a_position, 0.0, 1.0);
 9		}
10		`;
11
12		const fragmentSource = `
13		precision mediump float;
14		void main() {
15			gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
16		}
17		`;
18
19		const vs = this.createShader(this.gl.VERTEX_SHADER, vertexSource);
20		const fs = this.createShader(this.gl.FRAGMENT_SHADER, fragmentSource);
21
22		const program = this.gl.createProgram();
23		if (!program) throw new Error('Program create failed');
24
25		this.gl.attachShader(program, vs);
26		this.gl.attachShader(program, fs);
27		this.gl.linkProgram(program);
28
29		if (!this.gl.getProgramParameter(program, this.gl.LINK_STATUS)) {
30			throw new Error(this.gl.getProgramInfoLog(program) ?? 'Link error');
31		}
32
33		this.program = program;
34	}
35
36	use() {
37		this.gl.useProgram(this.program);
38	}
39
40	getAttribLocation(name: string): number {
41		return this.gl.getAttribLocation(this.program, name);
42	}
43
44	private createShader(type: number, source: string): WebGLShader {
45		const shader = this.gl.createShader(type);
46		if (!shader) throw new Error('Shader create failed');
47
48		this.gl.shaderSource(shader, source);
49		this.gl.compileShader(shader);
50
51		if (!this.gl.getShaderParameter(shader, this.gl.COMPILE_STATUS)) {
52			throw new Error(this.gl.getShaderInfoLog(shader) ?? 'Compile error');
53		}
54
55		return shader;
56	}
57}
  • توحد هذه الفئة إنشاء وربط واستخدام شادر الرؤوس وشادر القطع، مما يتيح لجانب الرسم استخدام الشادر بأمان۔
  • من خلال إتاحة دالة getAttribLocation، يمكن الوصول إليها بأمان من جانب شكل الميش (Mesh)۔

فئة لإدارة الميش (بيانات الرؤوس)

سننشئ أيضًا فئة لإدارة بيانات الرؤوس۔ من خلال تقسيم فئات الميش وفقًا للكائنات المرسومة، يصبح التوسع سهلًا۔

 1export class TriangleMesh {
 2	private buffer: WebGLBuffer;
 3	private vertexCount = 3;
 4
 5	constructor(private gl: WebGLRenderingContext) {
 6		const vertices = new Float32Array([
 7			0.0,  0.5,
 8			-0.5, -0.5,
 9			0.5, -0.5,
10		]);
11
12		const buffer = gl.createBuffer();
13		if (!buffer) throw new Error('Buffer create failed');
14
15		this.buffer = buffer;
16
17		gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
18		gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
19	}
20
21	draw(shader: ShaderProgram) {
22		const gl = this.gl;
23		const positionLoc = shader.getAttribLocation('a_position');
24
25		gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer);
26		gl.enableVertexAttribArray(positionLoc);
27		gl.vertexAttribPointer(positionLoc, 2, gl.FLOAT, false, 0, 0);
28
29		gl.drawArrays(gl.TRIANGLES, 0, this.vertexCount);
30	}
31}
  • تدير هذه الفئة بيانات رؤوس المثلث، ومن خلال ربطها بالشادر، تتولى عملية الرسم الفعلي۔
  • جميع المعلومات اللازمة للرسم محفوظة ضمن TriangleMesh۔

نقطة البداية (مثال على الاستخدام)

أخيرًا، اجمع الفئات لتشغيل التطبيق۔

1const canvas = document.getElementById('gl') as HTMLCanvasElement;
2
3const app = new GLApp(canvas);
4app.initialize();
5app.render();
  • مع هذا البناء، يصبح من السهل إضافة الرسوم المتحركة أو عدة أشكال ميش۔

نصائح عملية لكتابة ويب جي إل في تايب سكريبت

يوفر استخدام تايب سكريبت مع ويب جي إل المزايا التالية:۔

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

الملخص

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

يمكنك متابعة المقالة أعلاه باستخدام Visual Studio Code على قناتنا على YouTube.۔ يرجى التحقق من القناة على YouTube أيضًا.۔

YouTube Video