fix: render gallery skybox unlit double-sided

This commit is contained in:
Tom Boullay
2026-05-25 17:53:46 +02:00
parent 6a412c7b00
commit d7351e5f37
3 changed files with 64 additions and 15 deletions
+2 -2
View File
@@ -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.
+57 -10
View File
@@ -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 ? (
<SkyModelContent modelPath={fallbackModelPath} scale={fallbackScale} />
<SkyModelContent
materialSide={materialSide}
modelPath={fallbackModelPath}
scale={fallbackScale}
unlit={unlit}
/>
) : null;
return (
<SkyModelErrorBoundary key={modelPath} fallback={fallback}>
<SkyModelContent modelPath={modelPath} scale={scale} />
<SkyModelContent
materialSide={materialSide}
modelPath={modelPath}
scale={scale}
unlit={unlit}
/>
</SkyModelErrorBoundary>
);
}
function SkyModelContent({
materialSide,
modelPath,
scale,
unlit,
}: SkyModelContentProps): React.JSX.Element {
const camera = useThree((state) => state.camera);
const groupRef = useRef<THREE.Group>(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<T extends THREE.Material>(material: T): T {
const skyMaterial = material.clone();
skyMaterial.side = THREE.BackSide;
function createSkyMaterial<T extends THREE.Material>(
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 {
+5 -3
View File
@@ -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({
<SkyModel
fallbackModelPath={GAME_SCENE_FALLBACK_SKY_MODEL_PATH}
fallbackScale={GAME_SCENE_FALLBACK_SKY_MODEL_SCALE}
materialSide={THREE.DoubleSide}
modelPath={GAME_SCENE_SKY_MODEL_PATH}
scale={GAME_SCENE_SKY_MODEL_SCALE}
unlit
/>
<ambientLight intensity={0.75} />
<directionalLight position={[6, 8, 4]} intensity={2.1} />
<Lighting />
<Bounds fit clip observe margin={1.35}>
<Center>
<GalleryModelPreview