Cara menulis efek tampilan berbasis shader menggunakan React Three Fiber dan GLSL

 – Beragampengetahuan
7 mins read

Cara menulis efek tampilan berbasis shader menggunakan React Three Fiber dan GLSL – Beragampengetahuan

Setelah melihat berbagai tampilan gambar yang dibuat oleh beberapa rekan saya di X, saya memutuskan untuk mencobanya dan membuatnya sendiri. Idenya adalah untuk mempraktikkan teknik R3F dan shader sambil membuat sesuatu yang dapat dengan mudah digunakan kembali di proyek lain.

Catatan: Anda dapat menemukan kode untuk semua langkah sebagai fork di repositori Github berikut.

Memulai Proyek

Proyek dasarnya adalah aplikasi ViteJS React sederhana dengan R3F Canvas dan paket-paket berikut diinstal:

three                // ThreeJS & R3F packages
@react-three/fiber 
@react-three/drei
motion               // Previously FramerMotion
leva                 // To add tweaks to our shader
vite-plugin-glsl     // For vite to work with .glsl files

Sekarang kita sudah siap, kita bisa mulai menulis shader pertama kita.

Buat shader gambar sederhana

Pertama, kita akan membuat vertex.glsl & fragment.glsl Dalam dua file terpisah tampilannya seperti ini:

// vertex.glsl
varying vec2 vUv;
void main()
{
  // FINAL POSITION
  gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);

  // VARYINGS
  vUv = uv;
}

Dan milik kita fragment.glsl Ini terlihat seperti ini:

uniform sampler2D uTexture;
varying vec2 vUv;
void main()
{
    // Apply texture
    vec3 textureColor = texture2D(uTexture, vUv).rgb;

    // FINAL COLOR
    gl_FragColor = vec4(textureColor, 1.0);
}

Di sini kita meneruskan UV dari mesh ke shader fragmen dan masuk texture2D Fungsi menerapkan tekstur gambar ke fragmen kita.

Sekarang setelah file shader dibuat, kita dapat membuat komponen utama:

import { shaderMaterial, useAspect, useTexture } from "@react-three/drei";
import { extend } from "@react-three/fiber";
import { useRef } from "react";
import * as THREE from "three";
import imageRevealFragmentShader from "../shaders/imageReveal/fragment.glsl";
import imageRevealVertexShader from "../shaders/imageReveal/vertex.glsl";

const ImageRevealMaterial = shaderMaterial(
  {
    uTexture: new THREE.Texture(),
  },
  imageRevealVertexShader,
  imageRevealFragmentShader,
  (self) => {
    self.transparent = true;
  }
);

extend({ ImageRevealMaterial });

const RevealImage = ({ imageTexture }) => {
  const materialRef = useRef();

  // LOADING TEXTURE & HANDLING ASPECT RATIO
  const texture = useTexture(imageTexture, (loadedTexture) => {
    if (materialRef.current) {
      materialRef.current.uTexture = loadedTexture;
    }
  });
  const { width, height } = texture.image;
  const scale = useAspect(width, height, 0.25);

  return (
    <mesh scale={scale}>
      <planeGeometry args={[1, 1, 32, 32]} />
      <imageRevealMaterial attach="material" ref={materialRef} />
    </mesh>
  );
};

export default RevealImage;

Disini kita membuat bahan dasar menggunakan shaderMaterial Berasal dari React Three Drei dan kemudian mengembangkannya menggunakan R3F untuk digunakan di komponen kami.

Kami kemudian memuat gambar yang diteruskan sebagai penyangga dan menangani skalanya berkat useAspect Kait dari React-Three/Drei.

Kita harus mendapatkan sesuatu seperti ini:

Gunakan shader untuk menampilkan gambar pada geometri planar

Tambahkan efek dasar

(Disebutkan secara khusus kepada Bruno Simon karena telah menjadi inspirasinya).

Sekarang kita perlu menambahkan efek noise radial yang akan kita gunakan untuk menampilkan gambar kita, untuk melakukan ini kita akan menggunakan fungsi noise Perlin dan memadukannya dengan gradien radial seperti ini:

// fragment.glsl
uniform sampler2D uTexture;
uniform float uTime;

varying vec2 vUv;

#include ../includes/perlin3dNoise.glsl

void main()
{
    // Displace the UV
    vec2 displacedUv = vUv + cnoise(vec3(vUv * 5.0, uTime * 0.1));
    // Perlin noise
    float strength = cnoise(vec3(displacedUv * 5.0, uTime * 0.2 ));

    // Radial gradient
    float radialGradient = distance(vUv, vec2(0.5)) * 12.5 - 7.0;
    strength += radialGradient;

    // Clamp the value from 0 to 1 & invert it
    strength = clamp(strength, 0.0, 1.0);
    strength = 1.0 - strength;

    // Apply texture
    vec3 textureColor = texture2D(uTexture, vUv).rgb;

    // FINAL COLOR
    // gl_FragColor = vec4(textureColor, 1.0);
    gl_FragColor = vec4(vec3(strength), 1.0);
}

Anda dapat menemukan fungsi kebisingan Perlin di sini atau di repositori kode di sini.

ini uTime Digunakan untuk mengubah bentuk kebisingan pada waktunya agar terasa lebih hidup.

Sekarang kita hanya perlu sedikit memodifikasi komponen kita untuk meluangkan waktu pada materi kita:

const ImageRevealMaterial = shaderMaterial(
  {
    uTexture: new THREE.Texture(),
    uTime: 0,
  },
  ...
);
  
// Inside of the component
useFrame(({ clock }) => {
  if (materialRef.current) {
    materialRef.current.uTime = clock.elapsedTime;
  }
});

ini useFrame Hook R3F berjalan pada setiap frame dan memberi kita jam yang dapat kita gunakan untuk mendapatkan waktu yang telah berlalu sejak adegan tersebut dirender.

Inilah yang kami dapatkan sekarang:

Tekstur diperoleh dengan mencampurkan noise Perlin dan gradien radial
Tekstur diperoleh dengan menambahkan dan membalikkan noise dan gradien radial

Anda mungkin bisa melihatnya datang, tapi kita akan menggunakannya pada saluran Alpha dan kemudian mengurangi atau menambah radius gradien radial untuk menampilkan/menyembunyikan gambar.

Anda dapat mencobanya sendiri dengan menambahkan gambar ke saluran RGB warna akhir di fragment shader, dan menambahkan intensitas ke saluran Alpha. Anda harus mendapatkan sesuatu seperti ini:

Sekarang, bagaimana kita menganimasikan radius efeknya.

Efek animasi

Caranya, sebenarnya sangat mudah, kita tinggal menambahkan seragam baru saja uProgress Dalam shader fragmen kita, ini akan berubah dari 0 menjadi 1 dan menggunakannya untuk memengaruhi radius:

// fragment.glsl
uniform float uProgress;

...

// Radial gradient
float radialGradient = distance(vUv, vec2(0.5)) * 12.5 - 7.0 * uProgress;

...

// Opacity animation
float opacityProgress = smoothstep(0.0, 0.7, uProgress);

// FINAL COLOR
gl_FragColor = vec4(textureColor, strength * opacityProgress);

Kami juga menggunakan perkembangan untuk menambahkan sedikit animasi opacity di awal efek untuk menyembunyikan gambar kami sepenuhnya di awal.

Sekarang kita dapat meneruskan seragam baru ke material kita dan menggunakan Leva untuk mengontrol kemajuan efeknya:

const ImageRevealMaterial = shaderMaterial(
  {
    uTexture: new THREE.Texture(),
    uTime: 0,
    uProgress: 0,
  },
  ...
);

...
// LEVA TO CONTROL REVEAL PROGRESS
const { revealProgress } = useControls({
  revealProgress: { value: 0, min: 0, max: 1 },
});

// UPDATING UNIFORMS
useFrame(({ clock }) => {
  if (materialRef.current) {
    materialRef.current.uTime = clock.elapsedTime;
    materialRef.current.uProgress = revealProgress;
  }
});

Sekarang Anda harus memiliki sesuatu seperti ini:

Ada banyak cara berbeda yang bisa kita lakukan untuk maju. Untuk mempermudahnya, kami akan membuat tombol di aplikasi yang akan revealProgress Penggunaan prop komponen kami motion/react (sebelumnya Framer Motion):

// App.jsx

// REVEAL PROGRESS ANIMATION
const [isRevealed, setIsRevealed] = useState(false);
const revealProgress = useMotionValue(0);

const handleReveal = () => {
  animate(revealProgress, isRevealed ? 0 : 1, {
    duration: 1.5,
    ease: "easeInOut",
  });
  setIsRevealed(!isRevealed);
};

...

<Canvas>
  <RevealImage
    imageTexture="./img/texture.webp"
    revealProgress={revealProgress}
  />
</Canvas>

<button
  onClick={handleReveal}
  className="yourstyle"
>
  SHOW/HIDE
</button>

Kami menggunakan MotionValue dari Motion/React dan menyebarkannya ke props komponen kami.

Maka kita hanya perlu melakukannya useFrame Kait seperti ini:

// UPDATING UNIFORMS
useFrame(({ clock }) => {
  if (materialRef.current) {
    materialRef.current.uTime = clock.elapsedTime;
    materialRef.current.uProgress = revealProgress.get();
  }
});

Anda harus mendapatkan sesuatu seperti ini:

Tambahkan perpindahan

Hal lain yang ingin saya lakukan untuk menambahkan lebih banyak “kehidupan” pada efeknya adalah memindahkan simpul, menciptakan gelombang yang sinkron dengan kemajuan efeknya. Ini sebenarnya sangat sederhana, karena kita hanya perlu sedikit memodifikasi vertex shader kita:

uniform float uProgress;
varying vec2 vUv;

void main()
{
  vec3 newPosition = position;

  // Calculate the distance to the center of our plane
  float distanceToCenter = distance(vec2(0.5), uv);

  // Wave effect
  float wave = (1.0 - uProgress) * sin(distanceToCenter * 20.0 - uProgress * 5.0);

  // Apply the wave effect to the position Z
  newPosition.z += wave;

  // FINAL POSITION
  gl_Position = projectionMatrix * modelViewMatrix * vec4(newPosition, 1.0);

  // VARYINGS
  vUv = uv;
}

Di sini, intensitas dan posisi gelombang bergantung uProgress seragam.

Kita harus mendapatkan sesuatu seperti ini:

Ini cukup halus, tapi detail inilah yang membuat perbedaan menurut saya.

Melangkah lebih jauh

Itu di sini! Anda siap untuk pamer! Saya harap Anda bersenang-senang saat membuat efek ini. Sekarang Anda dapat mencoba segala macam hal dengannya untuk menjadikannya lebih baik dan melatih keterampilan shader Anda. Misalnya, Anda dapat mencoba menggunakan Leva untuk menambahkan lebih banyak penyesuaian guna mempersonalisasikannya sesuai keinginan Anda. Anda juga dapat mencoba menganimasikan pengguliran, membuat pesawat berputar, dan banyak lagi.

Saya memberikan contoh kecil tentang apa yang dapat Anda lakukan dengannya, yang dapat Anda temukan di akun Twitter saya.

Terima kasih telah membaca! 🙂

rencana pengembangan website



metode pengembangan website

jelaskan beberapa rencana untuk pengembangan website, proses pengembangan website, kekuatan dan kelemahan bisnis pengembangan website
, jasa pengembangan website, tahap pengembangan website, biaya pengembangan website

#Cara #menulis #efek #tampilan #berbasis #shader #menggunakan #React #Fiber #dan #GLSL

Tinggalkan Balasan

Alamat email Anda tidak akan dipublikasikan. Ruas yang wajib ditandai *