Water Shader
여름이 다가오고 있습니다 (적어도 이 레슨을 작성할 때는요), 수영장 파티를 열 시간이에요! 🩳
이 레슨에서는 React Three Fiber와 GLSL을 사용하여 다음과 같은 물 효과를 만들 것입니다:
거품은 수영장 가장자리와 오리 주변에 더 조밀합니다.
이 효과를 만들기 위해 셰이더 생성 작업을 단순화할 수 있는 Lygia Shader library를 발견하게 되며, 거품 효과를 생성하기 위해 render target 기법을 실습할 것입니다.
스타터 팩
이 레슨의 스타터 팩에는 다음과 같은 자산이 포함되어 있습니다:
- 수영장 모델 by Poly by Google CC-BY via Poly Pizza
- 오리 모델 from Pmndrs marketplace
- Inter 폰트 from Google Fonts
나머지는 간단한 조명과 카메라 설정으로 이루어져 있습니다.
수영장의 화창한 날 🏊
Water shader
Water는 <meshBasicMaterial />
을 적용한 단순한 평면입니다. 이 재료를 커스텀 shader로 대체하겠습니다.
새 파일 WaterMaterial.jsx
에 shader material용 보일러플레이트를 생성해 보겠습니다:
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
를 갖습니다.
커스텀 material을 선언적으로 사용하려면, main.jsx
파일에서 @react-three/fiber
의 extend
기능을 사용합니다:
// ... import { extend } from "@react-three/fiber"; import { WaterMaterial } from "./components/WaterMaterial.jsx"; extend({ WaterMaterial }); // ...
커스텀 material을 사용하는 컴포넌트보다 먼저 import된 파일에서
extend
호출을 해야 합니다. 이렇게 하면 우리의 컴포넌트에서WaterMaterial
을 선언적으로 사용할 수 있게 됩니다.그래서
WaterMaterial.jsx
파일 대신main.jsx
파일에서 이를 수행합니다.
이제 Water.jsx
에서 <meshBasicMaterial />
을 우리의 커스텀 material로 교체하고 관련된 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> ); };
기본 material을 커스텀 shader material로 성공적으로 대체했습니다.
Lygia Shader 라이브러리
애니메이션 폼 효과를 생성하기 위해 Lygia Shader 라이브러리를 사용할 것입니다. 이 라이브러리는 셰이더를 더 선언적으로 생성할 수 있게 하는 유틸리티와 함수 세트를 제공하여 셰이더 생성 과정을 단순화합니다.
우리가 관심을 가질 부분은 generative 섹션입니다. 이 섹션에는 노이즈, curl, fbm과 같은 생성 효과를 만드는 데 유용한 함수 세트가 포함되어 있습니다.
generative 섹션 내에서 사용할 수 있는 함수 목록을 찾을 수 있습니다.
하나의 함수를 열면 셰이더 내에서 사용할 수 있는 코드 조각과 효과의 미리보기를 볼 수 있습니다.
pnoise 함수 페이지
이것이 우리가 사용하고자 하는 효과입니다. 예제에서 pnoise
함수를 사용하기 위해 pnoise
셰이더 파일을 포함해야 함을 볼 수 있습니다. 우리도 동일하게 할 것입니다.
Lygia 해결
프로젝트에서 Lygia Shader 라이브러리를 사용하기 위해 두 가지 옵션이 있습니다:
- 라이브러리의 내용을 프로젝트로 복사하여 필요한 파일을 가져옵니다. (GLSL 파일을 가져오는 방법은 셰이더 소개 레슨에서 보았습니다.)
- 웹에서 Lygia Shader 라이브러리를 해결하고 lygia와 관련된
#include
지시문을 파일의 내용으로 자동으로 대체하는resolve-lygia
라는 라이브러리를 사용합니다.
프로젝트에 따라, 얼마나 많은 효과를 사용할 것인지, 다른 셰이더 라이브러리를 사용하는지에 따라 한 가지 해결 방법을 선호할 수 있습니다.
본 레슨에서는 resolve-lygia
라이브러리를 사용할 것입니다. 설치하려면 다음 명령어를 실행하세요:
yarn add resolve-lygia
그런 다음, fragment 및/또는 vertex 셰이더 코드를 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 셰이더에만 resolveLygia
함수를 사용하면 됩니다.
이제 우리의 셰이더에서 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
으로 곱하여 노이즈를 더 잘 보이게 하고, pnoise
함수를 사용하여 노이즈 효과를 생성합니다. 최종 색상을 생성하기 위해 노이즈 값에 따라 uColor
를 검정과 혼합합니다.
물에 적용된 노이즈 효과를 볼 수 있습니다.
⚠️ Resolve Lygia는 가끔 다운타임 문제가 있을 수 있습니다. 문제가 발생하면 Glslify library를 사용하거나 필요한 Lygia Shader 라이브러리의 파일을 복사하여 셰이더 소개 레슨에서 했던 것처럼 glsl 파일을 사용할 수 있습니다.
Foam effect
noise 효과는 우리의 폼 효과의 기초입니다. 효과를 미세 조정하기 전에 폼 효과에 대한 완전한 제어를 위해 필요한 uniforms를 생성해 봅시다.
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> }`) );
다음과 같은 uniforms를 추가했습니다:
uTime
: 폼 효과를 애니메이션화하기 위해uSpeed
: 효과 애니메이션의 속도를 제어하기 위해uRepeat
: noise 효과의 스케일링uNoiseType
: 다른 noise 함수 간 전환을 위해uFoam
: 폼 효과가 시작되는 임계값을 제어하기 위해uFoamTop
: 폼이 더 조밀해지는 임계값을 제어하기 위해
이제 이러한 uniforms를 소재에 적용해야 합니다.
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
함수를 사용하여 noise 효과를 생성합니다:
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;
noise 값에 따라 색상을 조정합니다:
vec3 finalColor = uColor; finalColor = mix(uColor, intermediateColor, step(0.01, noise)); finalColor = mix(finalColor, topColor, step(1.0, noise));
noise가 0.01
과 1.0
사이에 있을 때, 색상은 중간 색상이 됩니다. noise가 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> }
이제 물 위에 멋진 폼 효과가 적용되었습니다.
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
One-time payment. Lifetime updates included.