Wawa Sensei logo

Water Shader

Starter pack

गर्मी आ रही है (कम से कम जब मैं यह पाठ लिख रहा हूँ), यह पूल पार्टी का समय है! 🩳

इस पाठ में, हम React Three Fiber और GLSL का उपयोग करके निम्नलिखित जल प्रभाव बनाएंगे:

फोम पूल के किनारों और बत्तख के चारों ओर अधिक घना है।

इस प्रभाव को बनाने के लिए, हम Lygia Shader library की खोज करेंगे ताकि Shaders के निर्माण को सरल बनाया जा सके और हम फोम प्रभाव बनाने के लिए render target technique का अभ्यास करेंगे।

Starter pack

इस पाठ के लिए प्रारंभिक पैक में निम्नलिखित संपत्तियाँ शामिल हैं:

बाकी में सरल प्रकाश और कैमरा सेटअप शामिल है।

Starter pack

पूल में एक धूप का दिन 🏊

वॉटर शेडर

वॉटर सिर्फ एक साधारण plane है जिसमें <meshBasicMaterial /> लगाया गया है। हम इस material को एक custom shader से बदलेंगे।

आइए एक नया फाइल WaterMaterial.jsx बनाते हैं जिसमें shader material के लिए boilerplate होगा:

import { shaderMaterial } from "@react-three/drei";
import { Color } from "three";

export const WaterMaterial = shaderMaterial(
  {
    uColor: new Color("skyblue"),
    uOpacity: 0.8,
  },
  /*glsl*/ `
  varying vec2 vUv;
  void main() {
    vUv = uv;
    gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
  }`,
  /*glsl*/ ` 
    varying vec2 vUv;
    uniform vec3 uColor;
    uniform float uOpacity;

    void main() {
      gl_FragColor = vec4(uColor, uOpacity);
      #include <tonemapping_fragment>
      #include <encodings_fragment>
    }`
);

हमारे material में दो uniforms हैं: uColor और uOpacity.

हमारे custom material का declaratively उपयोग करने के लिए, main.jsx फाइल में @react-three/fiber से extend function का उपयोग करें:

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

extend({ WaterMaterial });

// ...

हमें extend कॉल को उस फाइल से करना होगा जो उस कंपोनेंट से पहले इम्पोर्ट की जाती है जो custom material का उपयोग करती है। इस तरह, हम अपने कंपोनेंट्स में WaterMaterial को declaratively उपयोग कर पाएंगे।

इसलिए हम इसे main.jsx फाइल में करते हैं, न कि WaterMaterial.jsx फाइल में।

अब Water.jsx में, हम <meshBasicMaterial /> को हमारे custom material से बदल सकते हैं और properties को संबंधित uniforms के साथ समायोजित कर सकते हैं:

import { useControls } from "leva";
import { Color } from "three";

export const Water = ({ ...props }) => {
  const { waterColor, waterOpacity } = useControls({
    waterOpacity: { value: 0.8, min: 0, max: 1 },
    waterColor: "#00c3ff",
  });

  return (
    <mesh {...props}>
      <planeGeometry args={[15, 32, 22, 22]} />
      <waterMaterial
        uColor={new Color(waterColor)}
        transparent
        uOpacity={waterOpacity}
      />
    </mesh>
  );
};

हमने सफलतापूर्वक basic material को हमारे custom shader material से बदल दिया है।

Lygia Shader Library

एनिमेटेड फोम इफ़ेक्ट बनाने के लिए, हम Lygia Shader library का उपयोग करेंगे। यह लाइब्रेरी shaders के निर्माण को सरल बनाती है, उपयोगिताओं और फ़ंक्शंस के सेट के साथ shaders को अधिक घोषणात्मक तरीके से बनाने की अनुमति देती है।

जो सेक्शन हमारे लिए रुचिकर होगा वह है generative। इसमें कई उपयोगी फ़ंक्शंस होते हैं जो जैसे noise, curl, fbm जैसे generative इफ़ेक्ट्स बनाने में सहायक होते हैं।

Lygia Shader library

जनरेटिव सेक्शन के भीतर, उपलब्ध फ़ंक्शंस की सूची पाई जा सकती है।

किसी एक फ़ंक्शन को खोलकर, आप देख सकते हैं कि इसे अपने shader में उपयोग करने के लिए कोड स्निपेट और इफ़ेक्ट का प्रीव्यू कैसे दिखता है।

Lygia Shader library function

pnoise फ़ंक्शन पेज

यह वही इफ़ेक्ट है जिसका हम उपयोग करना चाहते हैं। उदाहरण में आप देख सकते हैं कि pnoise फ़ंक्शन का उपयोग करने के लिए, उन्होंने pnoise shader फ़ाइल शामिल की है। हम भी ऐसा ही करेंगे।

Resolve Lygia

हमारे प्रोजेक्ट में Lygia Shader library का उपयोग करने के लिए, हमारे पास दो विकल्प हैं:

  • लाइब्रेरी की सामग्री को हमारे प्रोजेक्ट में कॉपी करें और जिन फ़ाइलों की आवश्यकता है उन्हें इंपोर्ट करें। (हमने shaders introduction lesson में GLSL फ़ाइलों को कैसे इंपोर्ट करें यह देखा था)
  • resolve-lygia नामक लाइब्रेरी का उपयोग करें जो वेब से Lygia Shader library को resolve करेगी और स्वचालित रूप से lygia से संबंधित #include निर्देशों को फ़ाइलों की सामग्री के साथ बदल देगी।

आपके प्रोजेक्ट पर निर्भर करते हुए, कितने इफ़ेक्ट्स का उपयोग करना है, और यदि आप अन्य shader लाइब्रेरीज़ का उपयोग कर रहे हैं, तो आप किसी एक समाधान को प्राथमिकता दे सकते हैं।

इस पाठ में, हम resolve-lygia लाइब्रेरी का उपयोग करेंगे। इसे इंस्टॉल करने के लिए, निम्नलिखित कमांड चलाएं:

yarn add resolve-lygia

फिर, इसका उपयोग करने के लिए हमें बस अपने fragment और/या vertex shader कोड को resolveLygia फ़ंक्शन के साथ लपेटना होगा:

// ...
import { resolveLygia } from "resolve-lygia";

export const WaterMaterial = shaderMaterial(
  {
    uColor: new Color("skyblue"),
    uOpacity: 0.8,
  },
  /*glsl*/ `
  varying vec2 vUv;
  void main() {
    vUv = uv;
    gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
  }`,
  resolveLygia(/*glsl*/ ` 
    varying vec2 vUv;
    uniform vec3 uColor;
    uniform float uOpacity;

    void main() {
      gl_FragColor = vec4(uColor, uOpacity);
      #include <tonemapping_fragment>
      #include <encodings_fragment>
    }`)
);

इस पाठ में हमें केवल fragment shader के लिए resolveLygia फ़ंक्शन का उपयोग करने की आवश्यकता होगी।

अब हम अपने shader में pnoise फ़ंक्शन का उपयोग कर सकते हैं:

#include "lygia/generative/pnoise.glsl"
varying vec2 vUv;
uniform vec3 uColor;
uniform float uOpacity;

void main() {
  float noise = pnoise(vec3(vUv * 10.0, 1.0), vec3(100.0, 24.0, 112.0));
  vec3 black = vec3(0.0);
  vec3 finalColor = mix(uColor, black, noise);
  gl_FragColor = vec4(finalColor, uOpacity);
  #include <tonemapping_fragment>
  #include <encodings_fragment>
}

हमने vUv को 10.0 से गुणा किया है ताकि noise अधिक स्पष्ट हो सके और हमने noise effect बनाने के लिए pnoise फ़ंक्शन का उपयोग किया है। हमने noise के मान के आधार पर uColor को ब्लैक के साथ मिलाया है ताकि अंतिम रंग बनाया जा सके।

Lygia Shader library pnoise

हम देख सकते हैं कि noise effect पानी पर लागू हुआ है।

⚠️ Resolve Lygia में कभी-कभी डाउनटाइम समस्याएं होती हैं। यदि आपको कोई समस्या आती है, तो आप Glslify library का उपयोग कर सकते हैं या Lygia Shader library से जिन फ़ाइलों की आपको आवश्यकता है उन्हें कॉपी कर सकते हैं और glsl files का उपयोग कर सकते हैं जैसा हमने shaders introduction lesson में किया था।

फ़ोम इफेक्ट

शोर प्रभाव (noise effect) हमारे फ़ोम प्रभाव का आधार है। प्रभाव को परिष्कृत करने से पहले, आइए उन यूनिफार्म्स को बनाते हैं जिनकी हमें फ़ोम प्रभाव पर पूर्ण नियंत्रण के लिए आवश्यकता होगी।

WaterMaterial.jsx:

import { shaderMaterial } from "@react-three/drei";
import { resolveLygia } from "resolve-lygia";
import { Color } from "three";

export const WaterMaterial = shaderMaterial(
  {
    uColor: new Color("skyblue"),
    uOpacity: 0.8,
    uTime: 0,
    uSpeed: 0.5,
    uRepeat: 20.0,
    uNoiseType: 0,
    uFoam: 0.4,
    uFoamTop: 0.7,
  },
  /*glsl*/ `
  varying vec2 vUv;
  void main() {
    vUv = uv;
    gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
  }`,
  resolveLygia(/*glsl*/ ` 
    #include "lygia/generative/pnoise.glsl"
    varying vec2 vUv;
    uniform vec3 uColor;
    uniform float uOpacity;
    uniform float uTime;
    uniform float uSpeed;
    uniform float uRepeat;
    uniform int uNoiseType;
    uniform float uFoam;
    uniform float uFoamTop;

    void main() {
      float noise = pnoise(vec3(vUv * 10.0, 1.0), vec3(100.0, 24.0, 112.0));
      vec3 black = vec3(0.0);
      vec3 finalColor = mix(uColor, black, noise);
      gl_FragColor = vec4(finalColor, uOpacity);
      #include <tonemapping_fragment>
      #include <encodings_fragment>
    }`)
);

हमने निम्नलिखित यूनिफार्म्स जोड़ीं:

  • uTime: फ़ोम प्रभाव को एनिमेट करने के लिए
  • uSpeed: प्रभाव एनिमेशन की गति को नियंत्रित करने के लिए
  • uRepeat: शोर प्रभाव को स्केल करने के लिए
  • uNoiseType: विभिन्न शोर कार्यों के बीच स्विच करने के लिए
  • uFoam: जब फ़ोम प्रभाव शुरू होता है उसे नियंत्रित करने के लिए
  • uFoamTop: वह सीमा जिस पर फ़ोम गाढ़ा हो जाता है उसे नियंत्रित करने के लिए

अब हमें इन यूनिफार्म्स को सामग्री पर लागू करने की आवश्यकता है। Water.jsx:

import { useFrame } from "@react-three/fiber";
import { useControls } from "leva";
import { useRef } from "react";
import { Color } from "three";

export const Water = ({ ...props }) => {
  const waterMaterialRef = useRef();
  const { waterColor, waterOpacity, speed, noiseType, foam, foamTop, repeat } =
    useControls({
      waterOpacity: { value: 0.8, min: 0, max: 1 },
      waterColor: "#00c3ff",
      speed: { value: 0.5, min: 0, max: 5 },
      repeat: {
        value: 30,
        min: 1,
        max: 100,
      },
      foam: {
        value: 0.4,
        min: 0,
        max: 1,
      },
      foamTop: {
        value: 0.7,
        min: 0,
        max: 1,
      },
      noiseType: {
        value: 0,
        options: {
          Perlin: 0,
          Voronoi: 1,
        },
      },
    });

  useFrame(({ clock }) => {
    if (waterMaterialRef.current) {
      waterMaterialRef.current.uniforms.uTime.value = clock.getElapsedTime();
    }
  });

  return (
    <mesh {...props}>
      <planeGeometry args={[15, 32, 22, 22]} />
      <waterMaterial
        ref={waterMaterialRef}
        uColor={new Color(waterColor)}
        transparent
        uOpacity={waterOpacity}
        uNoiseType={noiseType}
        uSpeed={speed}
        uRepeat={repeat}
        uFoam={foam}
        uFoamTop={foamTop}
      />
    </mesh>
  );
};

अब हम शेडर में फ़ोम लॉजिक बना सकते हैं।

हम uTime को uSpeed से गुणा करके अपने समायोजित समय की गणना करते हैं:

float adjustedTime = uTime * uSpeed;

फिर हम pnoise फ़ंक्शन का उपयोग करके शोर प्रभाव उत्पन्न करते हैं:

float noise = pnoise(vec3(vUv * uRepeat, adjustedTime * 0.5), vec3(100.0, 24.0, 112.0));

हम smoothstep फ़ंक्शन का उपयोग करके फ़ोम प्रभाव लागू करते हैं:

noise = smoothstep(uFoam, uFoamTop, noise);

फिर, हम फ़ोम का प्रतिनिधित्व करने के लिए चमकीले रंग बनाते हैं। हम एक intermediateColor और एक topColor बनाते हैं:

vec3 intermediateColor = uColor * 1.8;
vec3 topColor = intermediateColor * 2.0;

हम शोर मान के आधार पर रंग को समायोजित करते हैं:

vec3 finalColor = uColor;
finalColor = mix(uColor, intermediateColor, step(0.01, noise));
finalColor = mix(finalColor, topColor, step(1.0, noise));

जब शोर 0.01 और 1.0 के बीच होता है, तो रंग मध्यम रंग होगा। जब शोर 1.0 या उससे अधिक होता है, तो रंग शीर्ष रंग होगा।

यहाँ अंतिम शेडर कोड है:

#include "lygia/generative/pnoise.glsl"
varying vec2 vUv;
uniform vec3 uColor;
uniform float uOpacity;
uniform float uTime;
uniform float uSpeed;
uniform float uRepeat;
uniform int uNoiseType;
uniform float uFoam;
uniform float uFoamTop;

void main() {
  float adjustedTime = uTime * uSpeed;

  // NOISE GENERATION
  float noise = pnoise(vec3(vUv * uRepeat, adjustedTime * 0.5), vec3(100.0, 24.0, 112.0));

  // FOAM
  noise = smoothstep(uFoam, uFoamTop, noise);

  //  COLOR
  vec3 intermediateColor = uColor * 1.8;
  vec3 topColor = intermediateColor * 2.0;
  vec3 finalColor = uColor;
  finalColor = mix(uColor, intermediateColor, step(0.01, noise));
  finalColor = mix(finalColor, topColor, step(1.0, noise));

  gl_FragColor = vec4(finalColor, uOpacity);
  #include <tonemapping_fragment>
  #include <encodings_fragment>
}

अब हमारे पास पानी पर एक अच्छा फ़ोम प्रभाव है।

End of lesson preview

To get access to the entire lesson, you need to purchase the course.