مكتبه Three.js

مكتبه Three.js

في هذا البرنامج التعليمي، سوف تتعلم كيفية إنشاء تأثير إزاحة بكسل/شبكة باستخدام Three.js، مع تحسينه باستخدام تقنيات shaders وGPGPU. يغطي الدليل تطبيق تأثير إزاحة RGB الدقيق الذي يستجيب ديناميكيًا لحركة المؤشر. بحلول النهاية، ستكتسب فهمًا قويًا للتلاعب بالقوام وإنشاء تأثيرات بصرية تفاعلية في WebGL، وتوسيع قدراتك الإبداعية باستخدام Three.js.

createGeometry() {
    this.geometry = new THREE.PlaneGeometry(1, 1)
  }

  createMaterial() {
    this.material = new THREE.ShaderMaterial({
      vertexShader,
      fragmentShader,
      uniforms: {
        uTexture: new THREE.Uniform(new THREE.Vector4()),
        uContainerResolution: new THREE.Uniform(new THREE.Vector2(window.innerWidth, window.innerHeight)),
        uImageResolution: new THREE.Uniform(new THREE.Vector2()),
      },
    })
  }

  setTexture() {
    this.material.uniforms.uTexture.value = new THREE.TextureLoader().load(this.element.src, ({ image }) => {
      const { naturalWidth, naturalHeight } = image
      this.material.uniforms.uImageResolution.value = new THREE.Vector2(naturalWidth, naturalHeight)
    })
  }

  createMesh() {
    this.mesh = new THREE.Mesh(this.geometry, this.material)
  }

من المستحسن أن يكون لديك بعض الفهم الأساسي لـ Three.js وWebGL لفهم هذا البرنامج التعليمي. دعنا نتعمق!
الإعداد

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

أولاً، سوف نقوم بإنشاء مستوى Three.js أساسي باستخدام ShaderMaterial الذي سيعرض صورتنا ويضيفها إلى مشهد Three.js الخاص بنا.

varying vec2 vUv;

void main()
{
    vec4 modelPosition = modelMatrix * vec4(position, 1.0);
    vec4 viewPosition = viewMatrix * modelPosition;
    vec4 projectedPosition = projectionMatrix * viewPosition;
    gl_Position = projectedPosition;    

    vUv=uv;
}

لقد قمت بتمرير أبعاد Viewport إلى uContainerResolution uniform لأن شبكتي تشغل مساحة Viewport بالكامل. إذا كنت تريد أن يكون لصورتك حجم مختلف، فستحتاج إلى تمرير عرض وارتفاع عنصر HTML الذي يحتوي على الصورة.

هذا هو كود تظليل الرؤوس، والذي سيبقى دون تغيير لأننا لن نقوم بتعديل الرؤوس.

وهنا هو تظليل الجزء الأولي:

uniform sampler2D uTexture;

varying vec2 vUv;
uniform vec2 uContainerResolution;
uniform vec2 uImageResolution;


vec2 coverUvs(vec2 imageRes,vec2 containerRes)
{
    float imageAspectX = imageRes.x/imageRes.y;
    float imageAspectY = imageRes.y/imageRes.x;
    
    float containerAspectX = containerRes.x/containerRes.y;
    float containerAspectY = containerRes.y/containerRes.x;

    vec2 ratio = vec2(
        min(containerAspectX / imageAspectX, 1.0),
        min(containerAspectY / imageAspectY, 1.0)
    );

    vec2 newUvs = vec2(
        vUv.x * ratio.x + (1.0 - ratio.x) * 0.5,
        vUv.y * ratio.y + (1.0 - ratio.y) * 0.5
    );

    return newUvs;
}


void main()
{
    vec2 newUvs = coverUvs(uImageResolution,uContainerResolution);            
    
    vec4 image = texture2D(uTexture,newUvs);    

    gl_FragColor = image;
}

تعيد دالة coverUvs مجموعة من UVs التي ستجعل نسيج الصورة يتصرف مثل خاصية CSS object-fit: cover; . وهذه هي النتيجة:

تنفيذ الإزاحة باستخدام GPGPU

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

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

لحسن الحظ، يوفر Three.js أداة مساعدة تسمى GPUComputationRenderer. وهي تسمح لنا بإخراج برنامج تظليل مجزأ محسوب كنسيج واستخدام هذا النسيج كمدخل لبرنامج التظليل الخاص بنا في الإطار التالي. يُطلق على هذا اسم Buffer Texture. وإليك كيفية عمله:

أولاً، سنقوم بتهيئة مثيل GPUComputationRenderer. ولذلك سأقوم بإنشاء فئة تسمى GPGPU.

هذا هو في الأساس رمز إنشاء نموذج عام لنموذج GPUComputationRenderer.

نقوم بإنشاء النموذج في createGPGPURenderer.
نقوم بإنشاء كائن DataTexture في createDataTexture، والذي سيتم ملؤه بنتيجة shader المحسوب.
نقوم بإنشاء "متغير" في createVariable. يستخدم GPUComputationRenderer هذا المصطلح للإشارة إلى الملمس الذي سنخرجه. أعتقد أنه يُسمى بهذا الاسم لأن الملمس الخاص بنا سيختلف في كل إطار وفقًا لحساباتنا.
نقوم بتعيين تبعيات GPGPU.
نقوم بتهيئة النموذج الخاص بنا.
سنقوم الآن بإنشاء shader الشظية الذي سيستخدمه GPGPU الخاص بنا.

الملمس الحالي الذي ينشئه GPGPU الخاص بنا عبارة عن صورة حمراء عادية. لاحظ أننا لم نضطر إلى إعلان uniform sampler2D uGrid في رأس shader لأننا أعلناه كمتغير في مثيل GPUComputationRenderer.

الآن سنقوم باسترداد الملمس وتطبيقه على صورتنا.

هذا هو الكود الكامل لفئة GPGPU الخاصة بنا.

سيتم استدعاء طريقة العرض في كل إطار، وستعيد طريقة getTexture الملمس المحسوب لدينا.

في مادة المستوى الأول الذي أنشأناه، سنضيف موحدًا لـ uGrid. سيحتوي هذا الموحد على الملمس المسترد بواسطة GPGPU.

الآن سنقوم بتحديث هذا الزي في كل إطار بعد حساب نسيج GPGPU،

الآن، داخل برنامج تظليل الأجزاء في مستوى صورتنا الأولى، دعنا نعرض هذا الملمس

uniform sampler2D uGrid;

void main()
{
    vec2 newUvs = coverUvs(uImageResolution,uContainerResolution);            

    vec4 image = texture2D(uTexture,newUvs);    
    vec4 displacement = texture2D(uGrid,newUvs);

    gl_FragColor = displacement;
}

.