Retour à l'accueil

Rendu d'appartements 3D dans le navigateur sur Three.js

L'article décrit l'implémentation du rendu dans le navigateur d'appartements 3D avec meubles procéduraux sur Next.js et Three.js. Il couvre l'architecture de scène, l'éclairage, le multijoueur sur Socket.io, les optimisations et la gamification. Convient aux développeurs middle/senior.

Appartements 3D dans le navigateur : Three.js + meubles procéduraux
Advertisement 728x90

Rendu 3D d'appartements procéduraux dans le navigateur : Next.js, Three.js et multijoueur WebSocket

Dans le projet Prikolnya, des appartements 3D pixélisés sont rendus pour des arcades multijoueurs sans modèles externes. Il utilise Next.js 16 avec React Three Fiber, une génération procédurale de meubles à partir de primitives, des textures Canvas pour le contenu et Socket.io pour la synchronisation des positions. Les scènes avec 300 à 800 objets fonctionnent parfaitement sur les appareils modernes.

Architecture de scène et modèles d'appartements

Les appartements sont construits à partir de zones (pièces) avec des murs et des portes partagés. Les modèles sont définis par des données sans logique codée en dur :

type RoomDef = {
  id: string;
  name: string;
  x: number; z: number;
  width: number; depth: number;
  isSecret?: boolean;
};

type WallDef = {
  x1: number; z1: number;
  x2: number; z2: number;
  hasDoor?: boolean;
  doorOffset?: number;
};

const LOFT: ApartmentTemplate = {
  id: "loft",
  rooms: [
    { id: "lounge", name: "Salon", x: 0, z: 0, width: 12, depth: 8 },
    { id: "gaming", name: "Salle de jeu", x: 12, z: 0, width: 10, depth: 8 },
    { id: "bar", name: "Bar", x: 0, z: 8, width: 10, depth: 6 },
    { id: "studio", name: "Studio", x: 10, z: 8, width: 12, depth: 6 },
  ],
  walls: [
    { x1: 0, z1: 0, x2: 22, z2: 0 },
    { x1: 22, z1: 0, x2: 22, z2: 14 },
    { x1: 12, z1: 0, x2: 12, z2: 8, hasDoor: true },
  ],
};

Les modèles vont d'un studio (12x10) à un penthouse (24x22). Le rendu est déclaratif : les données sont transformées en objets Three.js.

Google AdInline article slot

Éclairage : couches et préréglages d'ambiance

La scène est éclairée avec trois couches :

  • Ambient — couche de base, dépend de l'heure de la journée (matin : #ffd4a0, intensité 1.1 ; nuit : #1a2244, 0.4).
  • Directionnel — lumière principale avec ombres + lumière de remplissage.
  • Lumières ponctuelles — une par pièce à une hauteur de 2,3 unités.

Huit préréglages d'ambiance sont ajoutés. Fête : plafond #ff2288 (1.8), ambient #110022 (0.2), accent #00ffff (3.0). Transformation complète sans changer la géométrie.

Meubles procéduraux à partir de primitives

Les meubles sont assemblés à partir de boxGeometry et cylinderGeometry. Avantages : aucun téléchargement, génération instantanée, simplicité pour les descriptions IA.

Google AdInline article slot

Exemple de borne d'arcade :

function ArcadeModel({ color, accent }: { color: string; accent: string }) {
  return (
    <group>
      <Block args={[0.7, 1.6, 0.7]} position={[0, 0.8, 0]} color={color} />
      <Block args={[0.5, 0.35, 0.05]} position={[0, 1.2, 0.33]}
             color="#0a0a2a" emissive={accent} emissiveIntensity={0.3} />
      <Block args={[0.5, 0.05, 0.25]} position={[0, 0.85, 0.25]}
             color="#1a1a2e" rotation={[-0.3, 0, 0]} />
      <mesh position={[-0.1, 0.92, 0.25]}>
        <cylinderGeometry args={[0.02, 0.02, 0.12]} />
        <meshStandardMaterial color="#ff2e63" />
      </mesh>
      {[0.05, 0.15].map((x) => (
        <mesh key={x} position={[x, 0.9, 0.2]}>
          <cylinderGeometry args={[0.025, 0.025, 0.02]} />
          <meshStandardMaterial color={accent} emissive={accent}
                                emissiveIntensity={0.5} />
        </mesh>
      ))}
    </group>
  );
}

Scène : 300–800 objets. L'IA génère du JSON avec des primitives pour les présentations de marque.

Contenu mural via CanvasTexture

Les affiches sont rendues dynamiquement :

Google AdInline article slot
  • Canvas 256x170.
  • Arrière-plan #1a1a2e, cadre dans la couleur de l'auteur, texte avec retour à la ligne.
  • THREE.CanvasTexture avec NearestFilter.
  • meshStandardMaterial avec emissiveIntensity: 0.05.
const canvas = document.createElement("canvas");
canvas.width = 256;
canvas.height = 170;
const ctx = canvas.getContext("2d")!;
ctx.fillStyle = "#1a1a2e";
ctx.strokeStyle = authorColor;
// retour à la ligne, dessin

const texture = new THREE.CanvasTexture(canvas);
texture.minFilter = THREE.NearestFilter;

3 emplacements par mur (0.25, 0.5, 0.75), hauteur 1,3 unités. Prend en charge les images, vidéo, audio.

Avatars et animaux de compagnie pixélisés

Les avatars sont des textures Canvas sur une affiche. Couches : corps (5 tons), yeux, pantalon (6), vêtements (10), cheveux (12), accessoires (10). Échelle 16x24 → 128x192, NearestFilter.

Les animaux de compagnie sont interpolés avec lerp 0.05 (proche) / 0.15 (loin).

Multijoueur avec interpolation

Socket.io diffuse les positions (~20 FPS) :

useFrame(() => {
  const now = Date.now();
  if (now - lastRef.current.t < 50) return;
  const dx = Math.abs(pos.x - lastRef.current.x);
  const dz = Math.abs(pos.z - lastRef.current.z);
  if (dx < 0.05 && dz < 0.05) return;

  socketRef.current?.volatile.emit("position", { x: pos.x, z: pos.z });
});

Interpolation : position.x += (target.x - position.x) * 0.15 (15% par image).

volatile.emit supprime les paquets obsolètes.

Météo et effets

Systèmes de particules sur BufferGeometry :

  • Pluie : 600 particules, lineSegments.
  • Neige : 400 particules, points avec dérive.
  • Étoiles : 200 particules, scintillement sur Y.
useFrame(() => {
  for (let i = 0; i < count; i++) {
    positions[i * 3 + 1] -= velocities[i];
    positions[i * 3] += Math.sin(Date.now() * 0.0008 + i * 0.7) * 0.004;
    if (positions[i * 3 + 1] < -0.5) {
      positions[i * 3 + 1] = 7 + Math.random() * 2;
    }
  }
  posAttr.needsUpdate = true;
});

Portes et animations

Ouverture à 81° (π*0.45) basée sur le produit scalaire de la normale du mur :

const wallNormal = { x: -(z2 - z1), z: x2 - x1 };
const toPlayer = { x: playerX - doorX, z: playerZ - doorZ };
const dot = wallNormal.x * toPlayer.x + wallNormal.z * toPlayer.z;
const direction = dot > 0 ? 1 : -1;
currentAngle += (targetAngle * direction - currentAngle) * 0.1;

Optimisations de performance

| Solution | Effet |

|---------|--------|

| dynamic(() => import("./Apartment3D"), { ssr: false }) | Three.js chargé à la demande, ~150 KB gzip |

| antialias: false | -15-20% de fillrate |

| dpr: Math.min(devicePixelRatio, 2) | Économise 2-4x sur HiDPI |

| NearestFilter | Pas de filtrage trilinéaire |

| castShadow uniquement sur les meubles | Ombres sélectives |

| receiveShadow uniquement sur le sol | Un seul récepteur |

Bundle 3D : ~600 KB brut, Three.js tree-shaken.

Ludification et projets

  • Économie : pièces pour les publications, limite 100/jour.
  • Niveaux : 10, jusqu'à 3500 XP.
  • Bâtiments croissants : 1-6 étages basés sur le contenu.
  • Événements : 8/an, x3 XP.
  • Pièces secrètes : 10 via codes.

Prochainement : meubles IA, trophées, instanciation/LOD.

Points clés à retenir

  • Les meubles procéduraux à partir de 4-15 primitives éliminent les téléchargements glTF.
  • Socket.io avec volatile.emit et lerp 15% assure un multijoueur fluide.
  • CanvasTexture + NearestFilter pour le contenu et les avatars de style pixel.
  • Les optimisations réduisent le bundle à 150 KB gzip avec 300-800 objets.
  • Les modèles déclaratifs permettent 5 variantes d'appartements sans code.

— Editorial Team

Advertisement 728x90

Lire ensuite