π λ€μ΄κ°λ©°
λλ μ§κΈ μ€ν¬λ‘€μ λ΄λ¦¬λ©΄μ λ΄ μκ°μ νλ‘μ νΈ λ±μ λ³Ό μ μλ
κ°μΈ ν¬νΈν΄λ¦¬μ€ μ¬μ΄νΈλ₯Ό λ§λλ μ€μ΄λ€.
κ·Έμ€μμλ κ°μ₯ 첫 λ²μ§Έ μΉμ
μΈ νμ΄λ‘ μΉμ
μμλ
μ΄μ μ λΈλ λλ‘ 3D λͺ¨λΈλ§νλ λ΄ μ΄λ¦ λ‘κ³ λ₯Ό λ λλ§ν΄μ 보μ¬μ£Όκ³ μΆμλ€ π€§
κ·Έλ¦¬κ³ μ΄λ, λ¨μν 3D λͺ¨λΈμ μ¬λ¦¬κ³ λλλ κ²μ΄ μλλΌ,
κΈ°λ³Έμ μΌλ‘λ μλμΌλ‘ μ΄μ§μ΄μ§ νμ μ νκ³ μλ,
μ¬μ©μκ° λ§μ°μ€λ‘ λλκ·Ένλ©΄, κ·Έ νμ μ μ¬μ©μ μ λ ₯μ λ°λΌ λ°μνκ³ ,
λ§μ°μ€λ₯Ό λλ μκ°, κ·Έ μνλ₯Ό κΈ°μ€μΌλ‘ λ€μ μλ νμ μ΄ μ΄μ΄μ§λ κ²μ μνλ€.
λν, μ²μμ μ¬μ΄νΈλ₯Ό λ€μ΄μ€λ μκ° λ‘κ³ κ° λ°λ‘ λ±μ₯νκ² νλλ λ무 λΆμμ°μ€λ¬μμ
μμ°μ€λ¬μμ μν΄ λΆλλ½κ² νμ΄λμΈ λλ©΄μ λ±μ₯ν μ μκ² λ§λ€κ³ μΆμλ€.
κ·Έλμ μ΄λ² ν¬μ€ν μμλ μΌλ¨ νμ΄λμΈμ μ΄λ»κ² ꡬννκ³ ,
μ΄λ»κ² μ΅μ ννλμ§λ₯Ό μ 리ν΄λ³΄λ €κ³ νλ€.
π₯ μ΄κΈ° νμ΄λμΈ μ½λ
μ²μ νμ΄λμΈμ ꡬννμ λ μ λ§ ,, μ§κ΄μ μΌλ‘ μ½λλ₯Ό μ§°λ€.
λΆλλ½μ§λ§ ,, μλ μ½λμμ λ³Ό μ μλ―μ΄
μ²μμλ μ¬μ§μ opacityλ₯Ό 0μΌλ‘ λ§λ€μ΄μ μ 보μ΄κ² νλ€κ°
λ§€ νλ μλ§λ€ opacityλ₯Ό μ‘°κΈμ© μ¦κ°μμΌμ 1μ κ°κΉμμ§λλ‘ λ§λ€μλ€ ,, π
useEffect(() => {
scene.traverse((obj) => {
if (obj instanceof Mesh && obj.material instanceof MeshStandardMaterial) {
obj.material.transparent = true;
obj.material.opacity = 0;
}
});
}, [scene]);
useFrame(() => {
scene.traverse((obj) => {
if (obj instanceof Mesh && obj.material instanceof MeshStandardMaterial) {
if (obj.material.opacity < 1) {
obj.material.opacity += 0.01;
}
}
});
});
μμ§ν μ΄λκΉμ§λ§ ν΄λ 'μ€ ! λ΄κ° μνλλ‘ μ λνλλκ΅° !! μκ°λ³΄λ€ μ½λ€ !? π' νλ€.
νμ§λ§ λμ€μ R3Fμ Three.js κ΄λ ¨ κΈλ€μ μ’ λ μ°Ύμ보λ
μ΄ μ½λμλ λ κ°μ§μ ν° λ¬Έμ κ° μλ€λ μ¬μ€μ κΉ¨λ«κ² λμλ€.
λ¬Έμ μ 1) λ§€ νλ μλ§λ€ scene.traverseλ₯Ό νΈμΆνκ³ μμ
첫 λ²μ§Έ λ¬Έμ λ μ±λ₯ λΆλΆμ΄μλ€.
useFrame(() => {
scene.traverse((obj) => {
...
});
});
κΈ°μ‘΄ μ½λμ μλ μ΄ λΆλΆμ λ€μ 보면, λ§€ νλ μλ§λ€ μ€νλλ κ²μ μ μ μλ€.
μΌλ°μ μΌλ‘ μ΄λΉ 60νλ μ μ λλ‘ λκ³ μλ€κ³ μΉλ©΄, 1μ΄μ 60λ² scene.traverseλ₯Ό νΈμΆνκ³ μλ κ²μ΄λ€..
λ scene.traverseκ° λ¨μν ν¨μμ΄λ©΄ λͺ¨λ₯ΌκΉ
μ΄ ν¨μλ scene μμ μλ λͺ¨λ obejectλ₯Ό μ¬κ·μ μΌλ‘ νλ ν¨μλΌλκ² λ¬Έμ μλ€.
μλ₯Ό λ€μ΄ GLTF λͺ¨λΈ μμ Meshκ° 10κ°μ΄λ©΄ 10κ°λ₯Ό λ§€ νλ μλ§λ€ νμνλ κ±°κ³ ,
100κ°λ©΄ 100κ°λ₯Ό λ§€ νλ μλ§λ€ νμνλ κ²μ΄λ€.
λ°λΌμ opacityλ₯Ό μ½κ° μ¬λ €μ£Όκ³ μΆλ€λ μ΄μ λ§μΌλ‘ λ§€ νλ μ μ 체 ꡬ쑰λ₯Ό νκ³ μλ€λ κ² λ무 λΉν¨μ¨μ μΈ κ² κ°μμ
μ΄ λΆλΆμ λν΄μ κ°μ μ ν νμκ° μλ€κ³ μκ°νλ€.
ππ» κ·Έλμ λ΄κ° μκ°ν μ΄ λ¬Έμ μ ν΄κ²°λ²μ 'mountλ λ ν λ²λ§ traverse νλλ‘ νμ'μλ€.
λ¬Έμ μ 2) νλ μ μμ‘΄μ μΈ μ λλ©μ΄μ
λ λ²μ§Έ λ¬Έμ λ μ λλ©μ΄μ μ μλμλ€.
if (obj.material.opacity < 1) {
obj.material.opacity += 0.01;
}
μ΄ λ°©μμ κ²°κ΅ ν νλ μμ opacityλ₯Ό 0.01μ© μ¦κ°νλ κ²μΈλ°, νλ μ μλ νκ²½λ§λ€ λ€λ₯΄λ€λ κ²μ΄ λ¬Έμ μλ€.
μ΄λ€ κΈ°κΈ°μμλ 60fpsμΌ μλ μμ§λ§, μ΄λ€ νκ²½μμλ 30fps μ΄νμΌ μλ μκ³ ,
λΈλΌμ°μ νμ΄ λ°±κ·ΈλΌμ΄λλ‘ κ°λ©° νλ μ λ μ΄νΈκ° λ¨μ΄μ§ μλ μκΈ° λλ¬Έμ
60fpsμμλ λ 빨리 νμ΄λμΈ λκ³ , 30fpsμμλ λ λ°° λλ¦¬κ² νμ΄λμΈ λλ λ±
κΈ°κΈ° μ±λ₯μ λ°λΌ μ λλ©μ΄μ μ κΈΈμ΄κ° λ¬λΌμ§λ νμμ΄ μκΈΈ μ μλ€.
ππ» λ°λΌμ ν΄κ²°μ± μΌλ‘ μ΄ κ΅¬μ‘°λ₯Ό νλ μ μκ° μλ μκ°μ κΈ°μ€μΌλ‘ μμ§μ΄λλ‘ μμ νκ³ μ νλ€.
π§ μμ κ³Όμ
μμμ μ‘μ μμ λͺ©νλ₯Ό μ 리ν΄λ³΄λ©΄ μλμ κ°λ€.
- scene.traverseλ λ± ν λ²λ§ νλ€.
- μ λλ©μ΄μ μ νλ μ μκ° μλλΌ μκ°μ κΈ°μ€μΌλ‘ μμ§μ΄κ² νλ€.
κ·Έλ¦¬κ³ , μ΄λ₯Ό μν΄ κ°μ₯ λ¨Όμ ν μμ μ΄ μ¬μ§μ ν λ²λ§ λͺ¨μλκ² νλ κ²μ΄μλ€.
μλλ©΄ λ§€λ² traverseλ₯Ό λλ μ΄μ κ° κ²°κ΅ μ΄λ€ meshμ μ΄λ€ materialμ opacityλ₯Ό μ μ©ν΄μΌ νλμ§λ₯Ό μ°ΎκΈ° μν¨μ΄κΈ° λλ¬Έμ΄λ€.
κ·Έλ κΈ°μ ν λ²λ§ μ°Ύμμ μ μ₯ν΄λκ³ , κ·Έ μ΄νμλ κ·Έ λͺ©λ‘λ§ λλ©΄ μ΄ λ¬Έμ λ₯Ό ν΄κ²°ν μ μμ κ±°λΌ μκ°νκ³ ,
λ°λΌμ μλμ κ°μ΄ νμ΄λμΈ λμμ΄ λλ μ¬μ§λ€μ λͺ¨μλκΈ° μν refλ₯Ό μΆκ°ν΄μ£Όμλ€.
const materialsRef = useRef<MeshStandardMaterial[]>([]);
κ·Έλ¦¬κ³ , useEffectμμ λ§μ΄νΈ μ λ± ν λ²λ§ scene.traverseλ₯Ό λλλ‘ μμ ν΄μ£Όμλ€.
useEffect(() => {
const collected: MeshStandardMaterial[] = [];
scene.traverse((obj) => {
if (obj instanceof Mesh) {
obj.castShadow = true;
obj.receiveShadow = false;
}
if (isMeshWithStandardMaterial(obj)) {
const material = obj.material;
material.transparent = true;
material.opacity = 0;
material.needsUpdate = true;
collected.push(material);
}
});
materialsRef.current = collected;
}, [scene]);
μ¬κΈ°μ νλ μΌμ ν¬κ² λ κ°μ§μ΄λ€.
- κ·Έλ¦Όμ μ€μ
- obj.castShadoe = true;
- obj.receiveShadow = false;
- νμ΄λμΈ λμ μ¬μ§ μμ§
- MeshStandardMaterial νμ μ μ¬μ§λ§ 골λΌμ
- transparent = true, opacity = 0μΌλ‘ μ΄κΈ°ννκ³
- materialsRef.current λ°°μ΄μ λͺ¨μλλ€.
μ΄λ κ² ν΄μ€μΌλ‘μ¨ μ΄ μμ μ λ§μ΄νΈ νμ΄λ°μ λ± ν λ²λ§ μΌμ΄λκ² λλ€.
μ΄ν useFrameμμλ λμ΄μ scene.traverseλ₯Ό νΈμΆν νμκ° μκ³ ,
κ·Έ λμ materialsRef.current λ°°μ΄μ λλ©΄μ opacity κ°λ§ μ λ°μ΄νΈνλ©΄ λλ€.
μ΄λ κ² μμ ν¨μΌλ‘μ¨ GLTFκ° μ무리 볡μ‘ν΄μ Έλ,
λ§€ νλ μ λΉμ©μ μ¬μ§μ κ°μ λ§νΌλ§ λμ΄λκ² μ νν μ μλλ‘ μ΅μ νκ° λ κ²μ΄λ€ !
λ€μμΌλ‘ κ³ μΉ λΆλΆμ opacity μ¦κ° λ‘μ§μ΄λ€.
κΈ°μ‘΄μλ μλμ κ°μ΄ νλ μλ§λ€ 0.01μ© μ¦κ°μν€κ³ μμλ€λ©΄,
opacity += 0.01;
μ΄μ λ μμ λͺ©νμ λ°λΌ 'λͺ μ΄ λμ 0μμ 1κΉμ§ λ°κΏ κ²μΈμ§'λ₯Ό κΈ°μ€μΌλ‘ μ μ΄ν μ μλλ‘ κ³ μΉκ³ μΆμλ€.
κ·Έλμ λ μ¬λ¦° μμ΄λμ΄κ° 'νμ΄λμΈμ΄ μμλλ μκ°μ κΈ°μ΅ν΄λκ³ ,
λ§€ νλ μλ§λ€ `νμ¬ μκ° - μμ μκ°`μ ν΄μ£Όλ©΄, κ·Έκ² κ³§ κ²½κ³Ό μκ°μμ μ΄μ©νμ' μλ€.
λ°λΌμ μ΄λ₯Ό μν΄ νμ΄λμΈμ μμ μμ μ μκ°μ μ μ₯ν μλμ refλ₯Ό νλ λ μΆκ°ν΄μ£Όμλ€.
const startTimeRef = useRef<number | null>(null);
κ·Έλ¦¬κ³ useFrameμμλ R3Fκ° μ 곡νλ clockμ μ΄μ©ν΄μ μλμ κ°μ΄ λ§λ€μ΄μ£Όμλ€.
useFrame(({ clock }) => {
if (materialsRef.current.length === 0) return;
if (startTimeRef.current === null) {
startTimeRef.current = clock.getElapsedTime();
}
const elapsed = clock.getElapsedTime() - startTimeRef.current;
const rawT = Math.min(1, elapsed / fadeDuration); // 0~1
const t = rawT * rawT * (3 - 2 * rawT); // λΆλλ¬μ΄ μ΄μ§
μ¬κΈ°μμ clock.getElapsedTime()μ μ¬μ΄ μμλ ν νλ₯Έ μκ°(μ΄ λ¨μ)μ΄κ³ ,
startTimeRef.currentμλ νμ΄λμΈ μμ μμ μ μκ°μ μ μ₯ν΄μ£Όμλ€.
κ·Έλ¦¬κ³ elapsedμλ μμ ν μΌλ§λ μ§λ¬λμ§λ₯Ό μ μ₯νμΌλ©°,
elapsed / fadeDurationμ ν΅ν΄ 0 ~ 1 μ¬μ΄μ λΉμ¨λ‘ μ κ·νν΄μ£Όμλ€.
(μλ₯Ό λ€μ΄μ fadeDuration = 2.5λΌ κ°μ νλ©΄, 0μ΄μΌ λ 0, 1.25μ΄μΌ λ 0.5, 2.5μ΄ μ΄μμΌ λ 1μ΄ λλ λλ)
λν, μλμ μμ μ μ©ν΄μ 0μμ 1λ‘ κ° λ μ²μκ³Ό λμ΄ μ’ λ λΆλλ¬μ΄ S-곑μ μ λ§λ€μ΄μ£Όλλ‘ νμλ€.
(μΌμ’ μ smoothstep ννλΌκ³ 보면 λλ€.)
const t = rawT * rawT * (3 - 2 * rawT);
κ·Έλ¦¬κ³ μ΄λ κ² κ³μ°ν΄λΈ tλ₯Ό opacityμ κ·Έλλ‘ λ°μν΄μ£Όμλ€.
materialsRef.current.forEach((mat) => {
mat.opacity = t;
mat.needsUpdate = true;
});
μ΄λ κ² ν΄μ€μΌλ‘μ¨ μ΄μ λ νλ μ μμ μκ΄ μμ΄ νμ κ°μ μκ° λμ νμ΄λμΈμ΄ μ§νλκ³ ,
κ°λ 0 ~ 1 μ¬μ΄λ₯Ό S-곑μ ννλ‘ λΆλλ½κ² μ±μμ§λλ‘ λμλ€.
λν λ§μ§λ§μΌλ‘λ, μ΄μ°¨νΌ opacityκ° 1μ΄ λ μ΄νμλ λμ΄μ κ³μ useFrameμμ μ²λ¦¬ν νμκ° μλ€κ³ μκ°ν΄μ
νμ΄λμΈμ΄ λλ¬μμ λνλ΄λ νλκ·Έλ₯Ό νλ λκ³ , κ·Έ μ΄νμλ useFrameμμ μ½λ°±μΌλ‘ λ°λ‘ return ν΄λ²λ¦¬λλ‘ λ§λ€μλ€.
(μμ κΉμ§λ§ μμ νμ λμ ꡬ쑰μμλ rawTκ° 1μ λλ¬ν μ΄νμλ κ³μ κ°μ κ°μ λ€μ opacityμ λ£κ³ μμλ€.)
const doneRef = useRef(false);
if (rawT >= 1) {
doneRef.current = true;
}
if (doneRef.current) return;
μ΄λ κ² μμ ν¨μΌλ‘μ¨ rawTκ° 1μ λλ¬νλ μκ° doneRef.current = trueλ‘ λ³κ²½νκ³ ,
κ·Έ μ΄ν νλ μλΆν°λ if (doneRef.current) return; μ κ±Έλ €μ early returnλλ€.
μ¦, νμ΄λμΈ μ λλ©μ΄μ μ΄ λλ μμ λΆν°λ μ¬μ€μ μ΄ μ»΄ν¬λνΈλ useFrameμμ μ무κ²λ νμ§ μλ μνκ° λλ€.
λ°λΌμ μ₯κΈ°μ μΌλ‘ 보면,
μ΄ νμ΄μ§λ₯Ό κ³μ μΌλκ³ μμ νκ±°λ νμ΄ μ€λ«λμ μ΄λ € μλ μν©μμλ λΆνμν μ°μ°μ μ΅μνν κ΅¬μ‘°κ° λλ€.
π¨ κ²°κ³Ό νλ©΄
'π»κ³΅λΆ κΈ°λ‘ > π Frontend' μΉ΄ν κ³ λ¦¬μ λ€λ₯Έ κΈ
| [Frontend] React Nativeμμ μμ°μ€λ¬μ΄ scroll fade ꡬννκΈ° - MaskedView (0) | 2025.11.24 |
|---|---|
| [Frontend] React Native + ESLINT v9 Flat Config (0) | 2025.11.17 |
| [Frontend] λΈλ λμμ λͺ¨λΈ μ μνκ³ μΉμΌλ‘ λμ°κΈ° (1) | 2025.11.14 |
| [Frontend] ESLint v9λ‘ TS+React λ¦°ν νκ²½ λ§λ€κΈ° (0) | 2025.11.12 |
| [Frontend] ESLint v9 곡μ λ¬Έμ λ―μ΄λ³΄κΈ° (0) | 2025.11.12 |