بناء شادر مع Babylon.js

نشرت: 2022-03-10
ملخص سريع ↬ تُعد Shaders مفهومًا رئيسيًا إذا كنت تريد إطلاق العنان للقوة الخام لوحدة معالجة الرسومات الخاصة بك. سأساعدك على فهم كيفية عملهم وحتى تجربة قوتهم الداخلية بطريقة سهلة ، وذلك بفضل Babylon.js . قبل التجربة ، يجب أن نرى كيف تعمل الأشياء داخليًا. عند التعامل مع الأجهزة ثلاثية الأبعاد التي يتم تسريعها بالأجهزة ، سيتعين عليك التعامل مع وحدتي CPU: وحدة المعالجة المركزية الرئيسية ووحدة معالجة الرسومات. وحدة معالجة الرسومات (GPU) هي نوع من أنواع وحدات المعالجة المركزية المتخصصة للغاية.

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

كيف يعمل؟

قبل التجربة ، يجب أن نرى كيف تعمل الأشياء داخليًا.

عند التعامل مع الأجهزة ثلاثية الأبعاد التي يتم تسريعها بالأجهزة ، سيتعين عليك التعامل مع وحدتي CPU: وحدة المعالجة المركزية الرئيسية ووحدة معالجة الرسومات. وحدة معالجة الرسومات (GPU) هي نوع من أنواع وحدات المعالجة المركزية المتخصصة للغاية.

مزيد من القراءة على SmashingMag:

  • بناء لعبة WebGL عبر منصة مع Babylon.js
  • استخدام واجهة برمجة تطبيقات Gamepad في ألعاب الويب
  • مقدمة في النمذجة المضلعة و Three.js
  • كيفية إنشاء آلة طبل متجاوبة 8 بت
المزيد بعد القفز! أكمل القراءة أدناه ↓

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

بمجرد تعيين جميع الحالات ، يمكن لوحدة المعالجة المركزية تحديد ما يجب تقديمه: الهندسة.

تتكون الهندسة من:

  • قائمة النقاط التي تسمى الرؤوس والمخزنة في مصفوفة تسمى المخزن المؤقت للقمة ،
  • قائمة الفهارس التي تحدد الوجوه (أو المثلثات) المخزنة في مصفوفة تسمى المخزن المؤقت للفهرس.

الخطوة الأخيرة لوحدة المعالجة المركزية هي تحديد كيفية تقديم الهندسة ؛ لهذه المهمة ، ستحدد وحدة المعالجة المركزية التظليل في وحدة معالجة الرسومات. التظليل عبارة عن أجزاء من التعليمات البرمجية التي ستنفذها وحدة معالجة الرسومات لكل من الرؤوس والبكسل التي يتعين عليها عرضها. (الرأس - أو الرؤوس عندما يكون هناك العديد منها - هي "نقطة" ثلاثية الأبعاد).

هناك نوعان من التظليل: تظليل الرأس و تظليل البكسل (أو الجزئي).

خط أنابيب الرسومات

قبل الحفر في التظليل ، دعنا نتراجع. لعرض وحدات البكسل ، ستأخذ وحدة معالجة الرسومات الشكل الهندسي الذي تحدده وحدة المعالجة المركزية وستقوم بما يلي:

  • باستخدام المخزن المؤقت للفهرس ، يتم تجميع ثلاث رؤوس لتحديد المثلث.
  • يحتوي المخزن المؤقت للفهرس على قائمة فهارس قمة الرأس. هذا يعني أن كل إدخال في المخزن المؤقت للفهرس هو رقم الرأس في المخزن المؤقت للرأس.
  • هذا مفيد حقًا لتجنب تكرار الرؤوس.

على سبيل المثال ، مخزن الفهرس المؤقت التالي عبارة عن قائمة من وجهين: [1 2 3 1 3 4]. يحتوي الوجه الأول على الرأس 1 والرأس 2 والرأس 3. ويحتوي الوجه الثاني على الرأس 1 والرأس 3 والرأس 4. لذلك ، هناك أربعة رؤوس في هذه الهندسة:

(عرض النسخة الكبيرة)

يتم تطبيق تظليل الرأس على كل رأس من رؤوس المثلث. الهدف الأساسي من تظليل الرأس هو إنتاج بكسل لكل رأس (الإسقاط على الشاشة ثنائية الأبعاد للرأس ثلاثي الأبعاد):

(عرض النسخة الكبيرة)

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

(عرض النسخة الكبيرة)

تتم هذه العملية لكل وجه محدد بواسطة المخزن المؤقت للفهرس.

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

GLSL

لقد رأينا للتو أنه لتقديم المثلثات ، تحتاج وحدة معالجة الرسومات إلى تظليلين: تظليل قمة الرأس وتظليل البكسل. تمت كتابة هذه المظلات بلغة تسمى Graphics Library Shader Language (GLSL). يبدو مثل C.

فيما يلي عينة من تظليل قمة الرأس الشائع:

 precision highp float; // Attributes attribute vec3 position; attribute vec2 uv; // Uniforms uniform mat4 worldViewProjection; // Varying varying vec2 vUV; void main(void) { gl_Position = worldViewProjection * vec4(position, 1.0); vUV = uv; }

هيكل Vertex Shader

يحتوي تظليل قمة الرأس على ما يلي:

  • السمات . تحدد السمة جزءًا من الرأس. بشكل افتراضي ، يجب أن يحتوي الرأس على الأقل على موضع ( vector3:x, y, z ). ومع ذلك ، بصفتك مطورًا ، يمكنك أن تقرر إضافة المزيد من المعلومات. على سبيل المثال ، في التظليل السابق ، يوجد vector2 يسمى uv (أي إحداثيات نسيج تسمح لك بتطبيق نسيج ثنائي الأبعاد على كائن ثلاثي الأبعاد).
  • زي موحد . المنتظم هو متغير يستخدمه التظليل ويتم تحديده بواسطة وحدة المعالجة المركزية. الزي الوحيد الذي لدينا هنا هو المصفوفة المستخدمة لإبراز موضع الرأس (x ، y ، z) على الشاشة (x ، y).
  • متنوع . المتغيرات المتغيرة هي القيم التي تم إنشاؤها بواسطة تظليل قمة الرأس ونقلها إلى تظليل البكسل. هنا ، سينقل تظليل الرأس قيمة vUV (نسخة بسيطة من uv ) إلى تظليل البكسل. هذا يعني أنه يتم تعريف البكسل هنا بإحداثيات الموضع والنسيج. سيتم استيفاء هذه القيم بواسطة وحدة معالجة الرسومات (GPU) واستخدامها بواسطة تظليل البكسل.
  • رئيسي . الوظيفة المسماة main هي الشفرة التي تنفذها وحدة معالجة الرسومات لكل رأس ويجب أن تنتج على الأقل قيمة لـ gl_position (موضع الرأس الحالي على الشاشة).

يمكننا أن نرى في عينتنا أن تظليل الرأس بسيط جدًا. يقوم بإنشاء متغير نظام (يبدأ بـ gl_ ) يسمى gl_position لتحديد موضع البكسل المرتبط ، ويقوم بتعيين متغير متغير يسمى vUV .

الشعوذة خلف المصفوفات

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

هذا هو أحد الأجزاء المعقدة لعمل ثلاثي الأبعاد. يجب أن تفهم الرياضيات المعقدة (أو سيتعين عليك استخدام محرك ثلاثي الأبعاد مثل Babylon.js ، والذي سنراه لاحقًا).

مصفوفة worldViewProjection عبارة عن مزيج من ثلاث مصفوفات مختلفة:

(عرض النسخة الكبيرة)

يتيح لنا استخدام المصفوفة الناتجة تحويل الرؤوس ثلاثية الأبعاد إلى بكسلات ثنائية الأبعاد ، مع مراعاة وجهة النظر وكل ما يتعلق بموضع الكائن الحالي وقياسه وتدويره.

هذه مسؤوليتك كمطور ثلاثي الأبعاد: إنشاء هذه المصفوفة وتحديثها باستمرار.

العودة إلى شادر

بمجرد تنفيذ تظليل الرأس على كل رأس (ثلاث مرات ، إذن) ، سيكون لدينا ثلاثة بكسلات مع وضع gl_position الصحيح وقيمة vUV . ستعمل وحدة معالجة الرسومات (GPU) على استيفاء هذه القيم على كل بكسل موجود في المثلث الناتج باستخدام هذه البكسلات.

بعد ذلك ، لكل بكسل ، سيتم تنفيذ تظليل البكسل:

 precision highp float; varying vec2 vUV; uniform sampler2D textureSampler; void main(void) { gl_FragColor = texture2D(textureSampler, vUV); }

بكسل (أو جزء) شادر هيكل

يشبه هيكل تظليل البكسل هيكل تظليل قمة الرأس:

  • متنوع . المتغيرات المتغيرة هي القيمة التي تم إنشاؤها بواسطة تظليل الرأس ويتم نقلها إلى تظليل البكسل. هنا ، سيحصل تظليل البكسل على قيمة vUV من تظليل قمة الرأس.
  • زي موحد . المنتظم هو متغير يستخدمه التظليل ويتم تحديده بواسطة وحدة المعالجة المركزية. الزي الوحيد الذي لدينا هنا هو أداة أخذ العينات ، وهي أداة تستخدم لقراءة ألوان النسيج.
  • رئيسي . الوظيفة المسماة main هي الشفرة التي تنفذها وحدة معالجة الرسومات لكل بكسل والتي يجب أن تنتج على الأقل قيمة لـ gl_FragColor (أي لون البكسل الحالي).

تظليل البكسل هذا بسيط إلى حد ما: يقرأ اللون من النسيج باستخدام إحداثيات النسيج من تظليل الرأس (والذي بدوره يحصل عليه من الرأس).

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

صعب جدا؟ BABYLON.Shader مادة للإنقاذ

أعلم ما تفكر فيه: "نظام Shaders رائع حقًا ، لكني لا أريد أن أتعبأ عناء أعمال السباكة الداخلية لـ WebGL أو حتى بالرياضيات."

وأنت على حق! هذا سؤال مشروع تمامًا ، وهذا هو بالضبط سبب إنشاء Babylon.js!

لاستخدام Babylon.js ، تحتاج أولاً إلى صفحة ويب بسيطة:

 <!DOCTYPE html> <html> <head> <title>Babylon.js</title> <script src="Babylon.js"></script> <script type="application/vertexShader"> precision highp float; // Attributes attribute vec3 position; attribute vec2 uv; // Uniforms uniform mat4 worldViewProjection; // Normal varying vec2 vUV; void main(void) { gl_Position = worldViewProjection * vec4(position, 1.0); vUV = uv; } </script> <script type="application/fragmentShader"> precision highp float; varying vec2 vUV; uniform sampler2D textureSampler; void main(void) { gl_FragColor = texture2D(textureSampler, vUV); } </script> <script src="index.js"></script> <style> html, body { width: 100%; height: 100%; padding: 0; margin: 0; overflow: hidden; margin: 0px; overflow: hidden; } #renderCanvas { width: 100%; height: 100%; touch-action: none; -ms-touch-action: none; } </style> </head> <body> <canvas></canvas> </body> </html>

ستلاحظ أن التظليل محدد بعلامات <script> . باستخدام Babylon.js ، يمكنك أيضًا تحديدها في ملفات منفصلة (ملفات .fx ).

  • مصدر Babylon.js
  • مستودع جيثب

أخيرًا ، رمز JavaScript الرئيسي هو هذا:

 "use strict"; document.addEventListener("DOMContentLoaded", startGame, false); function startGame() { if (BABYLON.Engine.isSupported()) { var canvas = document.getElementById("renderCanvas"); var engine = new BABYLON.Engine(canvas, false); var scene = new BABYLON.Scene(engine); var camera = new BABYLON.ArcRotateCamera("Camera", 0, Math.PI / 2, 10, BABYLON.Vector3.Zero(), scene); camera.attachControl(canvas); // Creating sphere var sphere = BABYLON.Mesh.CreateSphere("Sphere", 16, 5, scene); var amigaMaterial = new BABYLON.ShaderMaterial("amiga", scene, { vertexElement: "vertexShaderCode", fragmentElement: "fragmentShaderCode", }, { attributes: ["position", "uv"], uniforms: ["worldViewProjection"] }); amigaMaterial.setTexture("textureSampler", new BABYLON.Texture("amiga.jpg", scene)); sphere.material = amigaMaterial; engine.runRenderLoop(function () { sphere.rotation.y += 0.05; scene.render(); }); } };

يمكنك أن ترى أنني أستخدم BABYLON.ShaderMaterial للتخلص من عبء تجميع أدوات التظليل وربطها والتعامل معها.

عند إنشاء BABYLON.ShaderMaterial ، يجب عليك تحديد عنصر DOM المستخدم لتخزين التظليل أو الاسم الأساسي للملفات حيث توجد التظليل. إذا اخترت استخدام الملفات ، فيجب عليك إنشاء ملف لكل تظليل واستخدام النمط التالي: basename.vertex.fx و basename.fragment.fx . بعد ذلك ، سيتعين عليك إنشاء مادة مثل هذه:

 var cloudMaterial = new BABYLON.ShaderMaterial("cloud", scene, "./myShader", { attributes: ["position", "uv"], uniforms: ["worldViewProjection"] });

يجب عليك أيضًا تحديد أسماء السمات والزي الرسمي الذي تستخدمه.

بعد ذلك ، يمكنك تعيين قيم الزي الرسمي وأخذ العينات مباشرةً باستخدام setTexture و setFloat و setFloats و setColor3 و setColor4 و setVector2 و setVector3 و setVector4 و setMatrix .

بسيط جدا ، أليس كذلك؟

وهل تتذكر مصفوفة worldViewProjection السابقة ، باستخدام Babylon.js و BABYLON.ShaderMaterial . لا داعي للقلق بشأن ذلك! BABYLON.ShaderMaterial سوف يحسبها لك تلقائيًا لأنك ستعلنها في قائمة الزي الرسمي.

يمكن BABYLON.ShaderMaterial أيضًا التعامل مع المصفوفات التالية نيابة عنك:

  • world
  • view
  • projection
  • worldView ،
  • worldViewProjection .

لا حاجة للرياضيات بعد الآن. على سبيل المثال ، في كل مرة تقوم فيها بتنفيذ sphere.rotation.y += 0.05 ، سيتم إنشاء مصفوفة world الكرة لك وإرسالها إلى وحدة معالجة الرسومات.

شاهد النتيجة الحية لنفسك.

قم بإنشاء Shader الخاص بك (CYOS)

الآن ، دعنا نكبر وننشئ صفحة يمكنك من خلالها إنشاء التظليل الخاص بك ديناميكيًا ورؤية النتيجة على الفور. ستستخدم هذه الصفحة نفس الكود الذي ناقشناه سابقًا BABYLON.ShaderMaterial الكائن لتجميع وتنفيذ التظليل الذي ستنشئه.

لقد استخدمت محرر أكواد ACE لـ Create Your Own Shader (CYOS). إنه محرر كود رائع ، مع إبراز بناء الجملة. لا تتردد في إلقاء نظرة عليه.

باستخدام مربع التحرير والسرد الأول ، ستتمكن من تحديد تظليل محدد مسبقًا. سنرى كل منهم مباشرة بعد.

يمكنك أيضًا تغيير الشبكة (أي الكائن ثلاثي الأبعاد) المستخدمة لمعاينة التظليل باستخدام مربع التحرير والسرد الثاني.

يتم استخدام زر التجميع لإنشاء BABYLON.ShaderMaterial جديد من التظليل الخاص بك. الكود المستخدم بواسطة هذا الزر هو كما يلي:

 // Compile shaderMaterial = new BABYLON.ShaderMaterial("shader", scene, { vertexElement: "vertexShaderCode", fragmentElement: "fragmentShaderCode", }, { attributes: ["position", "normal", "uv"], uniforms: ["world", "worldView", "worldViewProjection"] }); var refTexture = new BABYLON.Texture("ref.jpg", scene); refTexture.wrapU = BABYLON.Texture.CLAMP_ADDRESSMODE; refTexture.wrapV = BABYLON.Texture.CLAMP_ADDRESSMODE; var amigaTexture = new BABYLON.Texture("amiga.jpg", scene); shaderMaterial.setTexture("textureSampler", amigaTexture); shaderMaterial.setTexture("refSampler", refTexture); shaderMaterial.setFloat("time", 0); shaderMaterial.setVector3("cameraPosition", BABYLON.Vector3.Zero()); shaderMaterial.backFaceCulling = false; mesh.material = shaderMaterial;

بسيط بشكل لا يصدق ، أليس كذلك؟ المواد جاهزة لإرسال ثلاث مصفوفات محسوبة مسبقًا ( world و worldView و worldViewProjection ). تأتي الرؤوس مع إحداثيات الموضع والإحداثيات العادية والنسيج. تم أيضًا تحميل زخارف لك:

amiga.jpg (عرض النسخة الكبيرة)
ref.jpg (عرض النسخة الكبيرة)

أخيرًا ، برنامج renderLoop هو المكان الذي أقوم فيه بتحديث زيين مناسبين:

  • واحد يسمى time ويحصل على بعض الرسوم المتحركة المضحكة.
  • الآخر يسمى cameraPosition ، والتي تحصل على موضع الكاميرا في التظليل الخاص بك (مفيد لمعادلات الإضاءة).

 engine.runRenderLoop(function () { mesh.rotation.y += 0.001; if (shaderMaterial) { shaderMaterial.setFloat("time", time); time += 0.02; shaderMaterial.setVector3("cameraPosition", camera.position); } scene.render(); });

شادر الأساسية

لنبدأ بأول تظليل محدد في CYOS: التظليل الأساسي.

نحن نعلم بالفعل هذا التظليل. يحسب gl_position ويستخدم إحداثيات النسيج لجلب لون لكل بكسل.

لحساب موضع البكسل ، نحتاج فقط إلى مصفوفة worldViewProjection الرأس:

 precision highp float; // Attributes attribute vec3 position; attribute vec2 uv; // Uniforms uniform mat4 worldViewProjection; // Varying varying vec2 vUV; void main(void) { gl_Position = worldViewProjection * vec4(position, 1.0); vUV = uv; }

يتم إرسال إحداثيات النسيج ( uv ) دون تعديل إلى تظليل البكسل.

يرجى ملاحظة أننا بحاجة إلى إضافة precision mediump float على السطر الأول لكل من تظليل الرأس والبكسل لأن Chrome يتطلب ذلك. وهي تحدد أنه ، من أجل أداء أفضل ، لا نستخدم قيم عائمة بدقة كاملة.

يعتبر تظليل البكسل أبسط ، لأننا نحتاج فقط إلى استخدام إحداثيات نسيج وجلب لون نسيج:

 precision highp float; varying vec2 vUV; uniform sampler2D textureSampler; void main(void) { gl_FragColor = texture2D(textureSampler, vUV); }

لقد رأينا سابقًا أن زي textureSampler ممتلئة بنسيج amiga . إذن فالنتيجة هي ما يلي:

(عرض النسخة الكبيرة)

شادر بالأبيض والأسود

دعنا نواصل مع تظليل جديد: تظليل أبيض وأسود. الهدف من هذا التظليل هو استخدام السابق ولكن مع وضع عرض بالأبيض والأسود فقط.

للقيام بذلك ، يمكننا الاحتفاظ بنفس تظليل الرأس. سيتم تعديل تظليل البكسل بشكل طفيف.

الخيار الأول لدينا هو أن نأخذ مكونًا واحدًا فقط ، مثل المكون الأخضر:

 precision highp float; varying vec2 vUV; uniform sampler2D textureSampler; void main(void) { gl_FragColor = vec4(texture2D(textureSampler, vUV).ggg, 1.0); }

كما ترى ، بدلاً من استخدام .rgb (تسمى هذه العملية swizzle) ، استخدمنا .ggg .

ولكن إذا أردنا الحصول على تأثير أبيض وأسود دقيق حقًا ، فإن حساب النصوع (الذي يأخذ في الاعتبار جميع المكونات) سيكون أفضل:

 precision highp float; varying vec2 vUV; uniform sampler2D textureSampler; void main(void) { float luminance = dot(texture2D(textureSampler, vUV).rgb, vec3(0.3, 0.59, 0.11)); gl_FragColor = vec4(luminance, luminance, luminance, 1.0); }

يتم حساب عملية dot (أو المنتج dot ) على النحو التالي: result = v0.x * v1.x + v0.y * v1.y + v0.z * v1.z

لذلك ، في حالتنا ، luminance = r * 0.3 + g * 0.59 + b * 0.11 . (تستند هذه القيم إلى حقيقة أن العين البشرية أكثر حساسية للون الأخضر.)

يبدو رائعًا ، أليس كذلك؟

(عرض النسخة الكبيرة)

تظليل الخلية

دعنا ننتقل إلى تظليل أكثر تعقيدًا: تظليل الخلية.

سيتطلب هذا منا الحصول على الرأس الطبيعي وموضع الرأس في تظليل البكسل. لذلك ، سيبدو تظليل قمة الرأس كما يلي:

 precision highp float; // Attributes attribute vec3 position; attribute vec3 normal; attribute vec2 uv; // Uniforms uniform mat4 world; uniform mat4 worldViewProjection; // Varying varying vec3 vPositionW; varying vec3 vNormalW; varying vec2 vUV; void main(void) { vec4 outPosition = worldViewProjection * vec4(position, 1.0); gl_Position = outPosition; vPositionW = vec3(world * vec4(position, 1.0)); vNormalW = normalize(vec3(world * vec4(normal, 0.0))); vUV = uv; }

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

تظليل البكسل كما يلي:

 precision highp float; // Lights varying vec3 vPositionW; varying vec3 vNormalW; varying vec2 vUV; // Refs uniform sampler2D textureSampler; void main(void) { float ToonThresholds[4]; ToonThresholds[0] = 0.95; ToonThresholds[1] = 0.5; ToonThresholds[2] = 0.2; ToonThresholds[3] = 0.03; float ToonBrightnessLevels[5]; ToonBrightnessLevels[0] = 1.0; ToonBrightnessLevels[1] = 0.8; ToonBrightnessLevels[2] = 0.6; ToonBrightnessLevels[3] = 0.35; ToonBrightnessLevels[4] = 0.2; vec3 vLightPosition = vec3(0, 20, 10); // Light vec3 lightVectorW = normalize(vLightPosition - vPositionW); // diffuse float ndl = max(0., dot(vNormalW, lightVectorW)); vec3 color = texture2D(textureSampler, vUV).rgb; if (ndl > ToonThresholds[0]) { color *= ToonBrightnessLevels[0]; } else if (ndl > ToonThresholds[1]) { color *= ToonBrightnessLevels[1]; } else if (ndl > ToonThresholds[2]) { color *= ToonBrightnessLevels[2]; } else if (ndl > ToonThresholds[3]) { color *= ToonBrightnessLevels[3]; } else { color *= ToonBrightnessLevels[4]; } gl_FragColor = vec4(color, 1.); }

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

هناك أربع خطوات رئيسية في هذا التظليل.

أولاً ، نعلن عن ثوابت العتبات والمستويات.

ثم نحسب الإضاءة باستخدام معادلة فونج (سنعتبر أن الضوء لا يتحرك):

 vec3 vLightPosition = vec3(0, 20, 10); // Light vec3 lightVectorW = normalize(vLightPosition - vPositionW); // diffuse float ndl = max(0., dot(vNormalW, lightVectorW));

تعتمد شدة الضوء لكل بكسل على الزاوية بين الاتجاه الطبيعي واتجاه الضوء.

ثم نحصل على لون نسيج البكسل.

أخيرًا ، نتحقق من العتبة ونطبق المستوى على اللون.

تبدو النتيجة ككائن كرتوني:

(عرض النسخة الكبيرة)

فونج شادر

استخدمنا جزءًا من معادلة Phong في التظليل السابق. دعونا نستخدمه بالكامل الآن.

من الواضح أن تظليل قمة الرأس بسيط هنا لأنه سيتم عمل كل شيء في تظليل البكسل:

 precision highp float; // Attributes attribute vec3 position; attribute vec3 normal; attribute vec2 uv; // Uniforms uniform mat4 worldViewProjection; // Varying varying vec3 vPosition; varying vec3 vNormal; varying vec2 vUV; void main(void) { vec4 outPosition = worldViewProjection * vec4(position, 1.0); gl_Position = outPosition; vUV = uv; vPosition = position; vNormal = normal; }

وفقًا للمعادلة ، يجب أن نحسب الأجزاء "المنتشرة" و "المرآوية" باستخدام اتجاه الضوء وطبيعي للرأس:

 precision highp float; // Varying varying vec3 vPosition; varying vec3 vNormal; varying vec2 vUV; // Uniforms uniform mat4 world; // Refs uniform vec3 cameraPosition; uniform sampler2D textureSampler; void main(void) { vec3 vLightPosition = vec3(0, 20, 10); // World values vec3 vPositionW = vec3(world * vec4(vPosition, 1.0)); vec3 vNormalW = normalize(vec3(world * vec4(vNormal, 0.0))); vec3 viewDirectionW = normalize(cameraPosition - vPositionW); // Light vec3 lightVectorW = normalize(vLightPosition - vPositionW); vec3 color = texture2D(textureSampler, vUV).rgb; // diffuse float ndl = max(0., dot(vNormalW, lightVectorW)); // Specular vec3 angleW = normalize(viewDirectionW + lightVectorW); float specComp = max(0., dot(vNormalW, angleW)); specComp = pow(specComp, max(1., 64.)) * 2.; gl_FragColor = vec4(color * ndl + vec3(specComp), 1.); }

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

نتيجة مجالنا:

(عرض النسخة الكبيرة)

تجاهل شادر

بالنسبة إلى التظليل المرتجع ، أود تقديم مفهوم جديد: discard الكلمة الأساسية.

يتجاهل هذا التظليل كل بكسل غير أحمر ويخلق وهمًا لكائن محفور.

تظليل قمة الرأس هو نفسه الذي يستخدمه التظليل الأساسي:

 precision highp float; // Attributes attribute vec3 position; attribute vec3 normal; attribute vec2 uv; // Uniforms uniform mat4 worldViewProjection; // Varying varying vec2 vUV; void main(void) { gl_Position = worldViewProjection * vec4(position, 1.0); vUV = uv; }

سيتعين على تظليل البكسل الموجود على جانبه اختبار اللون واستخدام الإهمال ، على سبيل المثال ، عندما يكون المكون الأخضر مرتفعًا جدًا:

 precision highp float; varying vec2 vUV; // Refs uniform sampler2D textureSampler; void main(void) { vec3 color = texture2D(textureSampler, vUV).rgb; if (color.g > 0.5) { discard; } gl_FragColor = vec4(color, 1.); }

والنتيجة مضحكة بعض الشيء:

(عرض النسخة الكبيرة)

موجة شادر

لقد لعبنا كثيرًا مع تظليل البكسل ، لكني أريد أيضًا أن أخبرك أنه يمكننا فعل الكثير باستخدام تظليل قمة الرأس.

بالنسبة إلى تظليل الموجة ، سنعيد استخدام Phong pixel shader.

سيستخدم تظليل قمة الرأس time المسمى للحصول على بعض القيم المتحركة. باستخدام هذا الزي ، سيولد التظليل موجة بمواضع الرؤوس:

 precision highp float; // Attributes attribute vec3 position; attribute vec3 normal; attribute vec2 uv; // Uniforms uniform mat4 worldViewProjection; uniform float time; // Varying varying vec3 vPosition; varying vec3 vNormal; varying vec2 vUV; void main(void) { vec3 v = position; vx += sin(2.0 * position.y + (time)) * 0.5; gl_Position = worldViewProjection * vec4(v, 1.0); vPosition = position; vNormal = normal; vUV = uv; }

يتم تطبيق الجيوب الأنفية على position.y ، وتكون النتيجة كما يلي:

(عرض النسخة الكبيرة)

رسم خرائط البيئة الكروية

هذا المقال مستوحى إلى حد كبير من مقالة "إنشاء انعكاس كروي / تظليل خرائط بيئي". سأدعك تقرأ هذا المقال الممتاز واللعب مع الظل المرتبط.

(عرض النسخة الكبيرة)

فرينل شادر

أود أن أنهي هذا المقال بمفضلتي: Fresnel shader.

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

تظليل قمة الرأس هو نفسه المستخدم بواسطة تظليل تظليل الخلية ، ويمكننا بسهولة حساب مصطلح Fresnel في تظليل البكسل (لأن لدينا الموضع الطبيعي وموضع الكاميرا ، والذي يمكن استخدامه لتقييم اتجاه العرض):

 precision highp float; // Lights varying vec3 vPositionW; varying vec3 vNormalW; // Refs uniform vec3 cameraPosition; uniform sampler2D textureSampler; void main(void) { vec3 color = vec3(1., 1., 1.); vec3 viewDirectionW = normalize(cameraPosition - vPositionW); // Fresnel float fresnelTerm = dot(viewDirectionW, vNormalW); fresnelTerm = clamp(1.0 - fresnelTerm, 0., 1.); gl_FragColor = vec4(color * fresnelTerm, 1.); } 
(عرض النسخة الكبيرة)

شادر بك؟

أنت الآن أكثر استعدادًا لإنشاء التظليل الخاص بك. لا تتردد في النشر في منتدى Babylon.js لمشاركة تجاربك!

إذا كنت تريد أن تذهب أبعد من ذلك ، فإليك بعض الروابط المفيدة:

  • Babylon.js ، الموقع الرسمي
  • Babylon.js ، مستودع جيثب
  • منتدى Babylon.js ، مطوري ألعاب HTML5
  • قم بإنشاء Shader الخاص بك (CYOS) ، Babylon.js
  • لغة تظليل OpenGL "ويكيبيديا
  • لغة تظليل OpenGL ، الوثائق