Gaussian Splatting, React Three Fiber

February 7, 2024

Gaussian Splatting 3D elem megjelenítése React Three Fiber-ben

A feladat során a React Three Fiber segítségével gaussian splatting-et fogunk megjeleníteni.

Fontos csomagok instalálása

POWERSHELL
npm create vite@latest

React + typescript + SWC választása

POWERSHELL
npm install three @types/three @react-three/fiber

A projekt miatt, még szükséges a következők instalálása:

POWERSHELL
npm install @react-three/drei

Alap beállítások

Ebbe a fejezetbe beállítjuk a környezetet és a dobozokat amibe az objektumokat fogjuk tárolni.

PLAIN TEXT
//src/index.css
* {
box-sizing: border-box;
}
html,
body,
#root {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}
body {
overscroll-behavior: none;
background: #f0f0f0;
font-family: "Inter";
}

App.tsx beállítás

A Canvas komponens létrehoz egy 3D renderelési teret, amelyen belül a 3D szcénánk fog megjelenni. A camera prop beállításai definiálják a kamera pozícióját, látószögét, és a renderelés mélységét (közeli és távoli klipping síkok). Egyszerű glb fájlok hozzáadása a Canvas-hoz amik a glb és gaussian splatting objektumokat fogják tárolni.

Az Environment komponens egy HDR (High Dynamic Range) képet használ a környezeti fényforrásként, ami realisztikusabb megvilágítást és reflexiókat biztosít. A Lightformer komponensek különböző formájú és intenzitású fényforrásokat adnak a szcénához, tovább növelve a vizuális hatást.

A ContactShadows komponens árnyékokat generál az objektumok alá, ami mélységet és térérzetet ad a szcénának. A CameraControls lehetővé teszi a felhasználó számára, hogy interaktívan mozogjon és zoomoljon a kamerával a 3D térben, növelve ezzel a szcéna interaktivitását.

A Preload komponens előtölti az összes szükséges erőforrást, hogy a felhasználói élmény gördülékeny legyen, és csökkentse a betöltési időt amikor a felhasználó először látogatja meg az oldalt.

TYPESCRIPT
//src/App.tsx
import { Canvas } from "@react-three/fiber";
import {
Preload,
Lightformer,
Environment,
CameraControls,
ContactShadows,
} from "@react-three/drei";
import Aquarium from "./components/Aquarium";
export default function App() {
return (
<Canvas
dpr={[1.5, 2]}
camera={{ position: [50, 5, -10], fov: 45, near: 1, far: 300 }}
>
<Aquarium position={[-0.1, 10.8, 3]} rotation={[Math.PI / 2, 0, 0]}>
</Aquarium>
<Aquarium position={[0, 0, 0]} rotation={[0, 0, 0]}>
</Aquarium>
<Environment
files="https://dl.polyhaven.org/file/ph-assets/HDRIs/hdr/1k/dancing_hall_1k.hdr"
resolution={1024}
>
{/** On top of the HDRI we add some rectangular and circular shapes for nicer reflections */}
<group rotation={[-Math.PI / 3, 0, 0]}>
<Lightformer
intensity={4}
rotation-x={Math.PI / 2}
position={[0, 5, -9]}
scale={[10, 10, 1]}
/>
{[2, 0, 2, 0, 2, 0, 2, 0].map((x, i) => (
<Lightformer
key={i}
form="circle"
intensity={4}
rotation={[Math.PI / 2, 0, 0]}
position={[x, 4, i * 4]}
scale={[4, 1, 1]}
/>
))}
<Lightformer
intensity={2}
rotation-y={Math.PI / 2}
position={[-5, 1, -1]}
scale={[50, 2, 1]}
/>
<Lightformer
intensity={2}
rotation-y={-Math.PI / 2}
position={[10, 1, 0]}
scale={[50, 2, 1]}
/>
</group>
</Environment>
<ContactShadows
smooth={false}
scale={100}
position={[0, -5.05, 0]}
blur={0.5}
opacity={0.75}
/>
<CameraControls
makeDefault
dollyToCursor
minPolarAngle={0}
maxPolarAngle={Math.PI / 2}
/>
<Preload all />
</Canvas>
);
}

A useRef hook segítségével egy referenciát hoz létre a csoport (group) objektumra, amit a szcénában lévő összes objektum tárolására és manipulálására használ. A useMask hook pedig egy stencil maszkot hoz létre, amit az objektumok anyagának manipulálására használ, hogy speciális renderelési effekteket érjen el, mint például a transzmissziós anyagok megjelenítése.

A useLayoutEffect hook segítségével a komponens beállítja az egyes mesh-ek (hálók) anyagát a maszkkal, amint a DOM felépült, biztosítva, hogy a 3D objektumok a megfelelő anyagbeállításokkal jelenjenek meg. Ez a folyamat dinamikusan módosítja az anyag tulajdonságait, így azok megfelelnek a stencil maszk által meghatározott viselkedésnek.

A MeshTransmissionMaterial egy speciális anyag, amit a @react-three/drei biztosít, és lehetővé teszi a fény áteresztését a 3D objektumon, létrehozva ezzel egy üvegszerű, átlátszó hatást, ami tökéletesen illik egy akvárium vizualizációjához. Ez az anyag számos paraméterrel rendelkezik, mint például a mintavételezés (samples), vastagság (thickness), kromatikus aberráció (chromaticAberration), és több más, ami lehetővé teszi a vizuális effektusok finomhangolását.

TYPESCRIPT
//src/components/Aquarium.tsx
import { useLayoutEffect, useRef } from "react";
import { MeshTransmissionMaterial, useGLTF, useMask } from "@react-three/drei";
import { Group, Mesh } from "three";
import { GLTF } from "three/examples/jsm/loaders/GLTFLoader.js";
type AquariumProps = JSX.IntrinsicElements["group"] & {
children: React.ReactNode;
};
interface GLTFResult extends GLTF {
nodes: {
[name: string]: THREE.Mesh;
};
animations: THREE.AnimationClip[];
}
const Aquarium: React.FC<AquariumProps> = ({ children, ...props }) => {
const ref = useRef<Group>(null);
const { nodes } = useGLTF("/shapes-transformed.glb") as unknown as GLTFResult;
const stencil = useMask(1, false);
useLayoutEffect(() => {
if (ref.current) {
ref.current.traverse((child: THREE.Object3D) => {
if ((child as Mesh).isMesh) {
// Now TypeScript knows 'child' is a Mesh, so 'child.material' is safe to access
const mesh = child as Mesh;
if (Array.isArray(mesh.material)) {
mesh.material.forEach((material) =>
Object.assign(material, stencil)
);
} else {
Object.assign(mesh.material, stencil);
}
}
});
}
}, [stencil]);
return (
<group {...props} dispose={null}>
<mesh
castShadow
scale={[0.61 * 6, 0.8 * 6, 1 * 6]}
geometry={nodes.Cube.geometry}
>
<MeshTransmissionMaterial
backside
samples={4}
thickness={3}
chromaticAberration={0.025}
anisotropy={0.1}
distortion={0.1}
distortionScale={0.1}
temporalDistortion={0.2}
iridescence={1}
iridescenceIOR={1}
iridescenceThicknessRange={[0, 1400]}
/>
</mesh>
<group ref={ref}>{children}</group>
</group>
);
};
export default Aquarium;

Az Aquarium komponens végül létrehoz egy csoportot, amely tartalmazza a 3D modellt és az opcionálisan hozzáadott gyermek komponenseket, lehetővé téve a felhasználó számára, hogy további elemeket helyezzen el az akváriumban, így növelve a szcéna komplexitását és interaktivitását.

A gltf és glb fájl hozzáadása

A GlbShoe funkcionális komponens a cipő modelljét és anyagát használja fel a useGLTF hook segítségével betöltött adatok alapján. A modell több különböző geometriai részből áll (például a fűzők, háló, talp stb.), amelyeket külön-külön adják hozzá a szcénához mesh elemekként. Mindegyik mesh elem saját geometriát és anyagot kap, valamint egy egyedi színt (material-color), ami lehetővé teszi a részletek kiemelését és a modell általános vizuális megjelenésének személyre szabását.

A ref hook segítségével egy referencia objektumot hoz létre a csoport (group) számára, ami lehetővé teszi a komponensben lévő összes mesh kezelését egyetlen egységként. A csoport pozícióját, méretarányát és forgatását is beállítja, hogy megfelelően jelenjen meg a szcénában.

TYPESCRIPT
//src/components/GlbShoe.tsx
import React, { useRef } from "react";
import { Float, useGLTF } from "@react-three/drei";
import { Group } from "three";
import { GLTF } from "three/examples/jsm/loaders/GLTFLoader.js";
const App: React.FC = () => {
return (
<>
<ambientLight intensity={0.3} />
<Float rotationIntensity={2} floatIntensity={10} speed={2}>
<GlbShoe />
</Float>
</>
);
};
type GLTFResult = GLTF & {
nodes: {
[name: string]: THREE.Mesh;
};
materials: {
[name: string]: THREE.Material;
};
};
const GlbShoe = () => {
const ref = useRef<Group>(null);
const { nodes, materials } = useGLTF(
"/shoe-draco.glb"
) as unknown as GLTFResult;
return (
<group
ref={ref}
scale={3}
position={[0, 0, 0]}
rotation={[-Math.PI / 2, Math.PI / 2, 0]}
>
<mesh
receiveShadow
castShadow
geometry={nodes.shoe.geometry}
material={materials.laces}
material-color="orange"
/>
<mesh
receiveShadow
castShadow
geometry={nodes.shoe_1.geometry}
material={materials.mesh}
material-color="red"
/>
<mesh
receiveShadow
castShadow
geometry={nodes.shoe_2.geometry}
material={materials.caps}
material-color="red"
/>
<mesh
receiveShadow
castShadow
geometry={nodes.shoe_3.geometry}
material={materials.inner}
material-color="orange"
/>
<mesh
receiveShadow
castShadow
geometry={nodes.shoe_4.geometry}
material={materials.sole}
material-color="white"
/>
<mesh
receiveShadow
castShadow
geometry={nodes.shoe_5.geometry}
material={materials.stripes}
material-color="orange"
/>
<mesh
receiveShadow
castShadow
geometry={nodes.shoe_6.geometry}
material={materials.band}
material-color="orange"
/>
<mesh
receiveShadow
castShadow
geometry={nodes.shoe_7.geometry}
material={materials.patch}
material-color="orange"
/>
</group>
);
};
export default App;

A Float komponens azt a hatást adja, hogy az objektum lágyan lebeg az alkalmazás 3D terében. Ez a viselkedés két paraméterrel szabályozható: a rotationIntensity (forgási intenzitás) és a floatIntensity (lebegési intenzitás) segítségével, valamint a speed (sebesség) paraméterrel, ami a lebegés sebességét állítja be.

A Splat komponens scale, position és rotation prop-okkal rendelkezik, amelyek lehetővé teszik a helyzetének, méretének és forgásának testreszabását a 3D térben. A src prop pedig a textúra URL-jét várja, amit a megjelenítéshez használ. Ebben az esetben a textúra forrása egy "https://huggingface.co/cakewalk/splat-data/resolve/main/nike.splat" URL, ami egy speciális formátumú fájl, ami a "splat" adatokat tartalmazza.

TYPESCRIPT
//src/components/GaussianShoe.tsx
import React from "react";
import { Float, Splat } from "@react-three/drei";
const App: React.FC = () => {
return (
<>
<ambientLight intensity={0.3} />
<Float rotationIntensity={2} floatIntensity={10} speed={2}>
<Splat
scale={2}
position={[4, 3, 1]}
rotation={[0, Math.PI / 2, 0]}
src="https://huggingface.co/cakewalk/splat-data/resolve/main/nike.splat"
/>
</Float>
</>
);
};
export default App;

Hozzáadjuk a két új fájlt az előzőleg létre hozott akváriumunkhoz

TYPESCRIPT
//src/App.tsx
...
import GaussianShoe from "./components/GaussianShoe";
import GlbShoe from "./components/GlbShoe";
export default function App() {
return (
<Canvas
dpr={[1.5, 2]}
camera={{ position: [50, 5, -10], fov: 45, near: 1, far: 300 }}
>
<Aquarium position={[-0.1, 10.8, 3]} rotation={[Math.PI / 2, 0, 0]}>
<GlbShoe />
</Aquarium>
<Aquarium position={[0, 0, 0]} rotation={[0, 0, 0]}>
<GaussianShoe />
</Aquarium>
...

Az általunk létrehozott két vizuális elem, egyrészt egy GLB formátumban tárolt cipő modell, másrészt egy Gaussian Splatting technikával előállított cipő, tökéletesen demonstrálja a két megközelítés közötti különbségeket és alkalmazási területeket. A GLB fájlból származó cipőmodell részletes és pontos megjelenítést nyújt, amely a digitális modellalkotás magas szintű precizitását tükrözi. Ez a fajta modell ideális választás lehet olyan alkalmazásokban, ahol a részletesség és a vizuális minőség elsődleges szempont, mint például a virtuális valóságban vagy a magas minőségű grafikát igénylő játékokban.

Ezzel szemben a Gaussian Splatting technikával előállított cipőmodell, bár kevésbé részletes, egy gyors és hatékony módszert kínál a 3D objektumok vizualizációjára. Ez a megközelítés különösen hasznos lehet olyan helyzetekben, ahol a sebesség és a hatékonyság fontosabb, mint a részletesség, például prototípusok gyors elkészítésénél.

A GLB modell előállítása általában több időt és erőforrást igényel, mivel részletes modellezési és textúrázási folyamatokon kell átesnie. Ezzel szemben a Gaussian Splatting egy viszonylag egyszerű algoritmussal képes gyorsan létrehozni a 3D elemeket, lehetővé téve az azonnali vizualizációt és iterációt.

Code

A teljes kód megtalálható:

https://github.com/balazsfaragodev/GaussianSplatting-ReactThreeFiber

Oszd meg ezt a cikket

Merülj el az izgalmas tudásban, amíg a buszra vársz!

Indítsd a napod a legújabb technológiai áttörésekkel. Csatlakozz most, és merülj el az innovációban!

Kapcsolódó Cikkek