From d7351e5f37de31544237f3237c5f179bc058f541 Mon Sep 17 00:00:00 2001 From: Tom Boullay Date: Mon, 25 May 2026 17:53:46 +0200 Subject: [PATCH] fix: render gallery skybox unlit double-sided --- docs/user/gallery.md | 4 +- src/components/three/world/SkyModel.tsx | 67 +++++++++++++++++++++---- src/pages/gallery/page.tsx | 8 +-- 3 files changed, 64 insertions(+), 15 deletions(-) diff --git a/docs/user/gallery.md b/docs/user/gallery.md index 1d94b5f..d14e59d 100644 --- a/docs/user/gallery.md +++ b/docs/user/gallery.md @@ -4,7 +4,7 @@ La galerie est disponible sur `/gallery`. Elle permet de parcourir les modèles ## Objectif -Cette page sert à remercier et valoriser le travail des designers du projet La Fabrik. Chaque modèle est affiché dans un canvas dédié, avec la même skybox que l'expérience principale pour garder une ambiance visuelle cohérente. +Cette page sert à remercier et valoriser le travail des designers du projet La Fabrik. Chaque modèle est affiché dans un canvas dédié, avec la même skybox et le même lighting que l'expérience principale. ## Utilisation @@ -19,7 +19,7 @@ Cette page sert à remercier et valoriser le travail des designers du projet La - Le viewer utilise `@react-three/fiber` et `@react-three/drei`. - `OrbitControls` permet de manipuler la caméra autour du modèle. - `Bounds` et `Center` recadrent automatiquement le modèle actif. -- `SkyModel` réutilise la skybox du jeu. +- `SkyModel` réutilise la skybox du jeu, avec un matériau non éclairé uniquement dans la galerie pour éviter que certaines faces deviennent noires avec une caméra orbitale libre. - Les animations GLTF présentes dans un modèle sont lancées automatiquement. - Un diagnostic simple inspecte les matériaux chargés pour signaler les textures absentes ou non exploitables. diff --git a/src/components/three/world/SkyModel.tsx b/src/components/three/world/SkyModel.tsx index 70fea1d..b976a1e 100644 --- a/src/components/three/world/SkyModel.tsx +++ b/src/components/three/world/SkyModel.tsx @@ -8,12 +8,16 @@ interface SkyModelProps { modelPath: string; fallbackModelPath?: string | undefined; fallbackScale?: number | undefined; + materialSide?: THREE.Side | undefined; scale?: number | undefined; + unlit?: boolean | undefined; } interface SkyModelContentProps { + materialSide: THREE.Side; modelPath: string; scale: number; + unlit: boolean; } interface SkyModelErrorBoundaryProps { @@ -54,23 +58,37 @@ class SkyModelErrorBoundary extends Component< export function SkyModel({ fallbackModelPath, fallbackScale = SKY_MODEL_SCALE, + materialSide = THREE.BackSide, modelPath, scale = SKY_MODEL_SCALE, + unlit = false, }: SkyModelProps): React.JSX.Element { const fallback = fallbackModelPath ? ( - + ) : null; return ( - + ); } function SkyModelContent({ + materialSide, modelPath, scale, + unlit, }: SkyModelContentProps): React.JSX.Element { const camera = useThree((state) => state.camera); const groupRef = useRef(null); @@ -78,7 +96,10 @@ function SkyModelContent({ scope: "SkyModel", scale, }); - const model = useMemo(() => createSkyModel(scene), [scene]); + const model = useMemo( + () => createSkyModel(scene, materialSide, unlit), + [materialSide, scene, unlit], + ); useEffect(() => { return () => { @@ -102,7 +123,11 @@ function SkyModelContent({ ); } -function createSkyModel(scene: THREE.Object3D): THREE.Object3D { +function createSkyModel( + scene: THREE.Object3D, + materialSide: THREE.Side, + unlit: boolean, +): THREE.Object3D { const model = scene.clone(true); model.traverse((object) => { @@ -112,20 +137,42 @@ function createSkyModel(scene: THREE.Object3D): THREE.Object3D { if (!(object instanceof THREE.Mesh)) return; object.material = Array.isArray(object.material) - ? object.material.map(createSkyMaterial) - : createSkyMaterial(object.material); + ? object.material.map((material) => + createSkyMaterial(material, materialSide, unlit), + ) + : createSkyMaterial(object.material, materialSide, unlit); }); return model; } -function createSkyMaterial(material: T): T { - const skyMaterial = material.clone(); - skyMaterial.side = THREE.BackSide; +function createSkyMaterial( + material: T, + materialSide: THREE.Side, + unlit: boolean, +): THREE.Material { + const skyMaterial = unlit + ? createUnlitSkyMaterial(material) + : material.clone(); + skyMaterial.side = materialSide; skyMaterial.depthTest = false; skyMaterial.depthWrite = false; - return skyMaterial as T; + return skyMaterial; +} + +function createUnlitSkyMaterial( + material: THREE.Material, +): THREE.MeshBasicMaterial { + const sourceMaterial = material as THREE.MeshStandardMaterial; + + return new THREE.MeshBasicMaterial({ + color: sourceMaterial.color?.clone() ?? new THREE.Color("#ffffff"), + map: sourceMaterial.map ?? null, + opacity: sourceMaterial.opacity, + toneMapped: false, + transparent: sourceMaterial.transparent, + }); } function disposeSkyModelMaterials(model: THREE.Object3D): void { diff --git a/src/pages/gallery/page.tsx b/src/pages/gallery/page.tsx index b3ea455..b32c670 100644 --- a/src/pages/gallery/page.tsx +++ b/src/pages/gallery/page.tsx @@ -23,13 +23,14 @@ import { } from "lucide-react"; import * as THREE from "three"; import { SkyModel } from "@/components/three/world/SkyModel"; +import { galleryModels, type GalleryModel } from "@/data/galleryModels"; import { GAME_SCENE_FALLBACK_SKY_MODEL_PATH, GAME_SCENE_FALLBACK_SKY_MODEL_SCALE, GAME_SCENE_SKY_MODEL_PATH, GAME_SCENE_SKY_MODEL_SCALE, } from "@/data/world/environmentConfig"; -import { galleryModels, type GalleryModel } from "@/data/galleryModels"; +import { Lighting } from "@/world/Lighting"; interface GalleryModelProps { model: GalleryModel; @@ -147,11 +148,12 @@ function GalleryScene({ - - +