WebGPU / TSL

Starter pack

WebGPU هو معيار ويب جديد يوفر واجهة برمجة تطبيقات منخفضة المستوى لعرض الرسوميات وإجراء الحسابات على وحدة معالجة الرسوميات (GPU). تم تصميمه ليكون خليفة لـ WebGL، ويقدم أداءً أفضل وميزات أكثر تقدمًا.

خبر سار، أصبح من الممكن الآن استخدامه مع Three.js مع تغييرات طفيفة في قاعدة التعليمات البرمجية.

في هذا الدرس، سوف نستكشف كيفية استخدام WebGPU مع Three.js وReact Three Fiber، وكيفية كتابة الشيدرز باستخدام Three Shading Language (TSL) الجديدة.

إذا كنت جديدًا على الشيدرز، أوصي بإكمال فصل Shaders أولاً قبل متابعة هذا الفصل.

WebGPU Renderer

لاستخدام WebGPU API بدلاً من WebGL، نحتاج إلى استخدام WebGPURenderer (لا يوجد قسم مخصص له حتى الآن في توثيق Three.js) بدلاً من WebGLRenderer.

مع React Three Fiber، عند إنشاء مكون <Canvas>، يتم إعداد الـ renderer تلقائيًا. ومع ذلك، يمكننا تجاوز الـ renderer الافتراضي عن طريق تمرير وظيفة إلى خاصية gl لمكون <Canvas>.

في App.jsx، لدينا مكون <Canvas> يستخدم WebGLRenderer الافتراضي. دعونا نقوم بتعديله لاستخدام WebGPURenderer بدلاً منه.

أولاً، نحتاج إلى إيقاف frameloop حتى يكون WebGPURenderer جاهزًا. يمكننا القيام بذلك عن طريق تعيين خاصية frameloop إلى never.

// ...
import { useState } from "react";

function App() {
  const [frameloop, setFrameloop] = useState("never");

  return (
    <>
      {/* ... */}
      <Canvas
        // ...
        frameloop={frameloop}
      >
        {/* ... */}
      </Canvas>
    </>
  );
}

export default App;

بعد ذلك، نحتاج إلى استيراد إصدار WebGPU من Three.js:

import * as THREE from "three/webgpu";

عند استخدام WebGPU، نحتاج إلى استخدام وحدة three/webgpu بدلاً من الوحدة الافتراضية three. ذلك لأن WebGPURenderer غير مدرج في البناء الافتراضي لـ Three.js.

ثم، يمكننا استخدام خاصية gl لإنشاء نسخة جديدة من WebGPURenderer:

// ...

function App() {
  const [frameloop, setFrameloop] = useState("never");

  return (
    <>
      {/* ... */}
      <Canvas
        // ...
        gl={(canvas) => {
          const renderer = new THREE.WebGPURenderer({
            canvas,
            powerPreference: "high-performance",
            antialias: true,
            alpha: false,
            stencil: false,
            shadowMap: true,
          });
          renderer.init().then(() => {
            setFrameloop("always");
          });
          return renderer;
        }}
      >
        {/* ... */}
      </Canvas>
    </>
  );
}
// ...

نقوم بإنشاء نسخة جديدة من WebGPURenderer ونمرر عنصر canvas إليها. نضبط أيضًا بعض الخيارات للـ renderer، مثل powerPreference، antialias، alpha، stencil، وshadowMap. هذه الخيارات مشابهة للخيارات المستخدمة في WebGLRenderer.

أخيرًا، نستدعي دالة init() للـ renderer لتجهيزه. بمجرد اكتمال التجهيز، نقوم بتعيين حالة frameloop إلى "always" لبدء العرض.

لنرى النتيجة في المتصفح:

يتم الآن عرض مكعبنا باستخدام WebGPURenderer بدلاً من WebGLRenderer.

الأمر بسيط جدًا كما ترى! لقد قمنا بتجهيز WebGPURenderer بنجاح في تطبيق React Three Fiber الخاص بنا. يمكنك الآن استخدام نفس واجهة برمجة التطبيقات (API) الخاصة بـ Three.js لإنشاء وتعديل الكائنات ثلاثية الأبعاد، تمامًا كما كنت تفعل مع WebGLRenderer.

أكبر التغييرات تأتي عند كتابة الشيدرز؛ حيث أن واجهة برمجة التطبيقات الخاصة بـ WebGPU تستخدم لغة تظليل مختلفة عن WebGL، وهذا يعني أننا نحتاج لكتابة الشيدرز بطريقة مختلفة، أي باستخدام WGSL بدلاً من GLSL.

هنا يأتي دور Three Shading Language (TSL).

لغة التظليل ثلاثية الأبعاد

TSL هي لغة تظليل جديدة مصممة للاستخدام مع Three.js لكتابة shaders بطريقة أكثر سهولة للمستخدم باستخدام نهج قائم على العقد.

ميزة كبيرة لـ TSL هي أنها غير معتمدة على محرك عرض معين، مما يعني أنه يمكنك استخدام نفس الـ shaders مع محركات عرض مختلفة، مثل WebGL و WebGPU.

هذا يجعل من السهل كتابة وصيانة الـ shaders، حيث لا تحتاج إلى القلق بشأن الفروقات بين لغتي التظليل.

كما أنها مقاومة للتغيير في المستقبل، حيث إذا تم إصدار محرك عرض جديد، يمكننا استخدام نفس الـ shaders بدون أي تغييرات طالما أن TSL تدعمه.

لا تزال لغة التظليل ثلاثية الأبعاد قيد التطوير، لكنها متاحة بالفعل في الإصدارات الأحدث من Three.js. أفضل طريقة لتعلمها ومتابعة التغييرات هي التحقق من صفحة الويكي لـThree Shading Language. لقد استخدمتها بشكل مكثف لتعلم كيفية استخدامها.

المواد القائمة على العقد

لفهم كيفية إنشاء shaders باستخدام TSL، نحتاج إلى فهم ما يعنيه أن يكون النهج قائماً على العقد.

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

هذا النهج له العديد من المزايا، مثل:

  • تمثيل بصري: يسهل فهم وتصور تدفق البيانات والعمليات في shader.
  • قابلية إعادة الاستخدام: يمكننا إنشاء عقد قابلة لإعادة الاستخدام يمكن استخدامها في shaders مختلفة.
  • المرونة: يمكننا بسهولة تعديل وتغيير سلوك shader عن طريق إضافة أو إزالة عقد.
  • القدرة على التوسع: إضافة/تخصيص الميزات من المواد الموجودة أصبح سهلاً الآن.
  • عدم التقيد بمحرك عرض معين: TSL ستولد الكود المناسب لمحرك العرض الهدف، سواء كان WebGL (GLSL) أو WebGPU (WGSL).

قبل أن نبدأ في كتابة الكود لأول مادة قائمة على العقد، يمكننا استخدام الـ Playground الخاص بـThree.js لتجربة النظام القائم على العقد بصريًا.

افتح الـ Playground الخاص بـThree.js وفي الأعلى، انقر على زر Examples، واختر المثال basic > fresnel.

Three.js playground

يجب أن ترى محرر مادة قائمة على العقد مع عقدتي color وعقدة float متصلة بعقدة fresnel. (“Color A”، “Color B”، و “Fresnel Factor”)

العقدة fresnel متصلة بلون مادة Basic Material مما ينتج عنه تلوين الإبريق بتأثير الفريسنيل.

Three.js playground

استخدم زر Splitscreen لمعاينة النتيجة على اليمين.

لنفترض أننا نريد التأثير على شفافية مادة Basic Material بناءً على الوقت. يمكننا إضافة عقدة Timer وربطها بعقدة Fract لإعادة ضبط الوقت إلى 0 بمجرد أن يصل إلى 1. ثم نقوم بتوصيله إلى مدخل opacity لمادة Basic Material.

الإبريق لدينا الآن يتلاشى للاختفاء قبل أن يتلاشى مرة أخرى.

خذ الوقت للعب مع العقد المختلفة ورؤية كيف تؤثر على المادة.

الآن لدينا فهم أساسي لكيفية عمل المادة القائمة على العقد، لنتعرف على كيفية استخدام المادة الجديدة القائمة على العقد من Three.js في React Three Fiber.

تنفيذ React Three Fiber

حتى الآن، مع WebGL، كنا نستخدم MeshBasicMaterial، MeshStandardMaterial، أو حتى ShaderMaterial المخصصة لإنشاء المواد.

عند استخدام WebGPU نحتاج إلى استخدام مواد جديدة متوافقة مع TSL. أسماؤها هي نفسها التي كنا نستخدمها من قبل مع Node قبل Material:

  • MeshBasicMaterial -> MeshBasicNodeMaterial
  • MeshStandardMaterial -> MeshStandardNodeMaterial
  • MeshPhysicalMaterial -> MeshPhysicalNodeMaterial
  • ...

لاستخدامها بطريقة إعلانية مع React Three Fiber، نحتاج إلى extend لها. في App.jsx:

// ...
import { extend } from "@react-three/fiber";

extend({
  MeshBasicNodeMaterial: THREE.MeshBasicNodeMaterial,
  MeshStandardNodeMaterial: THREE.MeshStandardNodeMaterial,
});
// ...

في الإصدارات المستقبلية من React Three Fiber، قد يتم ذلك تلقائيًا.

الآن يمكننا استخدام MeshBasicNodeMaterial وMeshStandardNodeMaterial الجديدتين في مكوناتنا.

لنقم باستبدال MeshStandardMaterial من المكعب في مكون Experience بـ MeshStandardNodeMaterial:

<mesh>
  <boxGeometry args={[1, 1, 1]} />
  <meshStandardNodeMaterial color="pink" />
</mesh>

WebGPU Pink Cube

يمكننا استخدام MeshStandardNodeMaterial تمامًا كما نستخدم MeshStandardMaterial.

المكعب لدينا يعتمد الآن على MeshStandardNodeMaterial بدلاً من MeshStandardMaterial. يمكننا الآن استخدام العقد لتخصيص المادة.

عقدة اللون

فلنتعلم كيفية إنشاء عقد مخصصة لتخصيص المواد لدينا باستخدام TSL.

أولاً، لنقم بإنشاء مكون جديد يسمى PracticeNodeMaterial.jsx في مجلد src/components.

export const PracticeNodeMaterial = ({
  colorA = "white",
  colorB = "orange",
}) => {
  return <meshStandardNodeMaterial color={colorA} />;
};

وفي Experience.jsx، استبدل مكعبنا بمستوى باستخدام PracticeNodeMaterial:

// ...
import { PracticeNodeMaterial } from "./PracticeNodeMaterial";

export const Experience = () => {
  return (
    <>
      {/* ... */}

      <mesh rotation-x={-Math.PI / 2}>
        <planeGeometry args={[2, 2, 200, 200]} />
        <PracticeNodeMaterial />
      </mesh>
    </>
  );
};

WebGPU Plane

لدينا الآن مستوى مع PracticeNodeMaterial.

لتخصيص المواد لدينا، يمكننا الآن التلاعب بالعقد المختلفة المتاحة لنا باستخدام عقد مختلفة. يمكن العثور على قائمة بالعقد المتاحة في صفحة الويكي.

لنبدأ ببساطة بعقدة colorNode لتغيير لون المواد لدينا. في PracticeNodeMaterial.jsx:

import { color } from "three/tsl";

export const PracticeNodeMaterial = ({
  colorA = "white",
  colorB = "orange",
}) => {
  return <meshStandardNodeMaterial colorNode={color(colorA)} />;
};

لقد قمنا بتعيين المقدار colorNode باستخدام العقدة color من وحدة three/tsl. تأخذ عقدة color لونًا كوسيطة وتعطي عقدة لون يمكن استخدامها في المواد.

هذا يعطينا نفس النتيجة كما كان من قبل، ولكن الآن يمكننا إضافة المزيد من العقد لتخصيص المواد لدينا.

لنقم باستيراد العقد mix و uv من وحدة three/tsl واستخدامها لمزج لونين استنادًا إلى إحداثيات UV للمستوى.

import { color, mix, uv } from "three/tsl";

export const PracticeNodeMaterial = ({
  colorA = "white",
  colorB = "orange",
}) => {
  return (
    <meshStandardNodeMaterial
      colorNode={mix(color(colorA), color(colorB), uv())}
    />
  );
};

سيقوم بتنفيذ العقد المختلفة للحصول على اللون النهائي للمواد. تأخذ عقدة mix لونين ومعامل (في هذه الحالة، إحداثيات UV) وتعطي لونًا مزيجًا من اللونين استنادًا إلى المعامل.

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

WebGPU Plane with Mix

يمكننا الآن رؤية اللونين مختلطين استنادًا إلى إحداثيات UV للمستوى.

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

تحديد العقد داخل السطر مناسب للمنطق البسيط جدًا، ولكن للمنطق الأكثر تعقيدًا، أوصي بتحديد العقد (وفي وقت لاحق uniforms والمزيد) في هوك useMemo:

// ...
import { useMemo } from "react";

export const PracticeNodeMaterial = ({
  colorA = "white",
  colorB = "orange",
}) => {
  const { nodes } = useMemo(() => {
    return {
      nodes: {
        colorNode: mix(color(colorA), color(colorB), uv()),
      },
    };
  }, []);

  return <meshStandardNodeMaterial {...nodes} />;
};

يقوم هذا بنفس الشيء تمامًا كما كان من قبل، ولكن الآن يمكننا إضافة المزيد من العقد إلى كائن nodes وتمريرها إلى meshStandardNodeMaterial بطريقة أكثر تنظيمًا/تعميمًا.

عن طريق تغيير خصائص colorA وcolorB، لن يتسبب ذلك في إعادة تجميع الشادر بفضل هوك useMemo.

لنقم بإضافة controls لتغيير ألوان المواد. في Experience.jsx:

// ...
import { useControls } from "leva";

export const Experience = () => {
  const { colorA, colorB } = useControls({
    colorA: { value: "skyblue" },
    colorB: { value: "blueviolet" },
  });
  return (
    <>
      {/* ... */}

      <mesh rotation-x={-Math.PI / 2}>
        <planeGeometry args={[2, 2, 200, 200]} />
        <PracticeNodeMaterial colorA={colorA} colorB={colorB} />
      </mesh>
    </>
  );
};

اللون الافتراضي يعمل بشكل صحيح، لكن تحديث الألوان ليس له أي تأثير.

هذا لأننا نحتاج إلى تمرير الألوان كـ uniforms إلى meshStandardNodeMaterial.

Uniformات

لإعلان الـ uniformات في TSL، يمكننا استخدام العقدة uniform من الوحدة three/tsl. تأخذ العقدة uniform قيمة كمعامل (يمكن أن تكون من أنواع مختلفة مثل float، vec3، vec4، إلخ) وتعيد عقدة uniform يمكن استخدامها في العقد المختلفة أثناء تحديثها من كود المكون لدينا.

لنقم بتغيير الألوان المرمزة بشكل صلب إلى uniformات في PracticeNodeMaterial.jsx:

// ...
import { uniform } from "three/tsl";

export const PracticeNodeMaterial = ({
  colorA = "white",
  colorB = "orange",
}) => {
  const { nodes, uniforms } = useMemo(() => {
    const uniforms = {
      colorA: uniform(color(colorA)),
      colorB: uniform(color(colorB)),
    };

    return {
      nodes: {
        colorNode: mix(uniforms.colorA, uniforms.colorB, uv()),
      },
      uniforms,
    };
  }, []);

  return <meshStandardNodeMaterial {...nodes} />;
};

نعلن عن كائن uniforms لتنظيم الكود بشكل أفضل، ونستخدم القيم من نوع uniform بدلاً من القيم الافتراضية التي حصلنا عليها عند إنشاء العقد الخاصة بنا.

عن طريق إرجاعهم في useMemo لدينا الآن إمكانية الوصول إلى الـ uniformات في مكوننا.

في useFrame يمكننا تحديث الـ uniformات:

// ...
import { useFrame } from "@react-three/fiber";

export const PracticeNodeMaterial = ({
  colorA = "white",
  colorB = "orange",
}) => {
  // ...

  useFrame(() => {
    uniforms.colorA.value.set(colorA);
    uniforms.colorB.value.set(colorB);
  });

  return <meshStandardNodeMaterial {...nodes} />;
};

استخدم طريقة value.set عندما تقوم بتحديث كائن uniform. على سبيل المثال، uniformات color أو vec3. بالنسبة للـ uniformات من نوع float، تحتاج إلى تعيين القيمة مباشرة: uniforms.opacity.value = opacity;

الألوان الآن يتم تحديثها بشكل صحيح في الوقت الفعلي.

قبل القيام بالمزيد مع الألوان، دعونا نرى كيف يمكننا تأثير المواقع vertices في مجسمنا باستخدام positionNode.

Node Position

تسمح لنا عقدة positionNode بالتأثير على موضع رؤوس الشكل الهندسي لدينا.

Three.js logoReact logo

React Three Fiber: The Ultimate Guide to 3D Web Development

✨ You have reached the end of the preview ✨

Go to the next level with Three.js and React Three Fiber!

Get full access to this lesson and the complete course when you enroll:

  • 🔓 Full lesson videos with no limits
  • 💻 Access to the final source code
  • 🎓 Course progress tracking & completion
  • 💬 Invite to our private Discord community
Unlock the Full Course – Just $85

One-time payment. Lifetime updates included.