Volver al inicio

Renderizado de apartamentos 3D en el navegador con Three.js

El artículo describe la implementación del renderizado en navegador de apartamentos 3D con muebles procedurales en Next.js y Three.js. Cubre arquitectura de escena, iluminación, multijugador en Socket.io, optimizaciones y gamificación. Adecuado para desarrolladores intermedios/senior.

Apartamentos 3D en el navegador: Three.js + muebles procedurales
Advertisement 728x90

Renderizado 3D Procedural de Apartamentos en el Navegador: Next.js, Three.js y Multijugador con WebSocket

En el proyecto Prikolnya, se renderizan apartamentos 3D pixelados para arcades multijugador sin modelos externos. Utiliza Next.js 16 con React Three Fiber, generación procedural de muebles a partir de primitivas, texturas Canvas para contenido y Socket.io para sincronización de posiciones. Las escenas con 300–800 objetos funcionan sin problemas en dispositivos modernos.

Arquitectura de Escena y Plantillas de Apartamentos

Los apartamentos se construyen a partir de zonas (habitaciones) con paredes y puertas compartidas. Las plantillas se definen mediante datos sin lógica codificada:

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: "Salón", x: 0, z: 0, width: 12, depth: 8 },
    { id: "gaming", name: "Sala de Juegos", x: 12, z: 0, width: 10, depth: 8 },
    { id: "bar", name: "Bar", x: 0, z: 8, width: 10, depth: 6 },
    { id: "studio", name: "Estudio", 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 },
  ],
};

Las plantillas van desde un estudio (12x10) hasta un ático (24x22). El renderizado es declarativo: los datos se transforman en objetos de Three.js.

Google AdInline article slot

Iluminación: Capas y Preajustes de Ambiente

La escena se ilumina con tres capas:

  • Ambiental — capa base, depende de la hora del día (mañana: #ffd4a0, intensidad 1.1; noche: #1a2244, 0.4).
  • Direccional — luz principal con sombras + luz de relleno.
  • Luces puntuales — una por habitación a una altura de 2.3 unidades.

Se añaden ocho preajustes de ambiente. Fiesta: techo #ff2288 (1.8), ambiental #110022 (0.2), acento #00ffff (3.0). Transformación completa sin cambiar la geometría.

Muebles Procedurales a partir de Primitivas

Los muebles se ensamblan a partir de boxGeometry y cylinderGeometry. Ventajas: sin descargas, generación instantánea, simplicidad para descripciones de IA.

Google AdInline article slot

Ejemplo de máquina 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>
  );
}

Escena: 300–800 objetos. La IA genera JSON con primitivas para exhibiciones de marca.

Contenido en Paredes mediante CanvasTexture

Los pósters se renderizan dinámicamente:

Google AdInline article slot
  • Canvas 256x170.
  • Fondo #1a1a2e, marco en color del autor, texto con ajuste de línea.
  • THREE.CanvasTexture con NearestFilter.
  • meshStandardMaterial con 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;
// ajuste de línea, dibujo

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

3 ranuras por pared (0.25, 0.5, 0.75), altura 1.3 unidades. Soporta imágenes, video, audio.

Avatares Pixelados y Mascotas

Los avatares son texturas Canvas en un billboard. Capas: cuerpo (5 tonos), ojos, pantalones (6), ropa (10), pelo (12), accesorios (10). Escala 16x24 → 128x192, NearestFilter.

Las mascotas se interpolan con lerp 0.05 (cerca) / 0.15 (lejos).

Multijugador con Interpolación

Socket.io transmite posiciones (~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 });
});

Interpolación: position.x += (target.x - position.x) * 0.15 (15% por fotograma).

volatile.emit descarta paquetes obsoletos.

Clima y Efectos

Sistemas de partículas en BufferGeometry:

  • Lluvia: 600 partículas, lineSegments.
  • Nieve: 400 partículas, points con deriva.
  • Estrellas: 200 partículas, parpadeo en 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;
});

Puertas y Animaciones

Apertura hasta 81° (π*0.45) basada en producto punto de la normal de la pared:

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;

Optimizaciones de Rendimiento

| Solución | Efecto |

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

| dynamic(() => import("./Apartment3D"), { ssr: false }) | Three.js cargado bajo demanda, ~150 KB gzip |

| antialias: false | -15-20% de tasa de relleno |

| dpr: Math.min(devicePixelRatio, 2) | Ahorra 2-4x en HiDPI |

| NearestFilter | Sin filtrado trilineal |

| castShadow solo en muebles | Sombras selectivamente |

| receiveShadow solo en suelo | Un receptor |

Bundle 3D: ~600 KB sin comprimir, Three.js con tree-shaking.

Gamificación y Planes

  • Economía: monedas por publicaciones, límite 100/día.
  • Niveles: 10, hasta 3500 XP.
  • Edificios en crecimiento: 1-6 plantas según contenido.
  • Eventos: 8/año, x3 XP.
  • Habitaciones secretas: 10 mediante códigos.

Próximamente: muebles con IA, trofeos, instanciación/LOD.

Conclusiones Clave

  • Muebles procedurales de 4-15 primitivas eliminan descargas glTF.
  • Socket.io con volatile.emit y lerp 15% asegura multijugador fluido.
  • CanvasTexture + NearestFilter para contenido y avatares estilo pixel.
  • Optimizaciones reducen bundle a 150 KB gzip con 300-800 objetos.
  • Plantillas declarativas permiten 5 variantes de apartamento sin código.

— Editorial Team

Advertisement 728x90

Leer después