返回首页

在浏览器中使用 Three.js 渲染 3D 公寓

本文描述了在 Next.js 和 Three.js 上实现浏览器渲染 3D 公寓与程序化家具的方法。它涵盖了场景架构、照明、Socket.io 上的多人游戏、优化以及游戏化。适用于中高级开发者。

浏览器中的 3D 公寓:Three.js + 程序化家具
Advertisement 728x90

浏览器中的程序化3D公寓渲染:Next.js、Three.js与WebSocket多人联机

Prikolnya项目中,像素风格的3D公寓被渲染用于多人街机游戏,无需外部模型。它使用Next.js 16与React Three Fiber,通过基本几何体程序化生成家具,Canvas纹理处理内容,Socket.io实现位置同步。包含300–800个对象的场景在现代设备上运行流畅。

场景架构与公寓模板

公寓由区域(房间)构成,共享墙壁和门。模板通过数据定义,无硬编码逻辑:

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: "客厅", x: 0, z: 0, width: 12, depth: 8 },
    { id: "gaming", name: "游戏室", x: 12, z: 0, width: 10, depth: 8 },
    { id: "bar", name: "吧台", x: 0, z: 8, width: 10, depth: 6 },
    { id: "studio", name: "工作室", 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 },
  ],
};

模板范围从单间公寓(12x10)到顶层公寓(24x22)。渲染是声明式的:数据被转换为Three.js对象。

Google AdInline article slot

光照:层级与氛围预设

场景采用三层光照:

  • 环境光 — 基础层,取决于时间(早晨:#ffd4a0,强度1.1;夜晚:#1a2244,强度0.4)。
  • 定向光 — 主光源带阴影 + 补光。
  • 点光源 — 每个房间一个,高度2.3单位。

添加了八种氛围预设。派对模式:天花板光 #ff2288(1.8),环境光 #110022(0.2),强调光 #00ffff(3.0)。无需改变几何体即可实现完全转换。

从基本几何体程序化生成家具

家具由boxGeometrycylinderGeometry组装而成。优势:无需下载、即时生成、便于AI描述。

Google AdInline article slot

示例街机模型:

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>
  );
}

场景包含300–800个对象。AI生成JSON,使用基本几何体进行品牌展示。

通过CanvasTexture实现墙面内容

海报动态渲染:

Google AdInline article slot
  • Canvas尺寸256x170。
  • 背景 #1a1a2e,边框使用作者颜色,文本自动换行。
  • THREE.CanvasTexture 使用 NearestFilter
  • meshStandardMaterial 设置 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;
// 文本换行,绘制

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

每面墙3个插槽(0.25、0.5、0.75位置),高度1.3单位。支持图像、视频、音频。

像素化角色与宠物

角色是广告牌上的Canvas纹理。层级:身体(5种色调)、眼睛、裤子(6种)、服装(10种)、发型(12种)、配饰(10种)。缩放16x24 → 128x192,使用NearestFilter

宠物使用线性插值,近距离0.05,远距离0.15。

带插值的多人联机

Socket.io广播位置(约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 });
});

插值:position.x += (target.x - position.x) * 0.15(每帧15%)。

volatile.emit 丢弃过时数据包。

天气与特效

基于BufferGeometry的粒子系统:

  • 雨:600个粒子,lineSegments
  • 雪:400个粒子,points带漂移。
  • 星星:200个粒子,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;
});

门与动画

基于墙壁法向量的点积,开门至81°(π*0.45):

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;

性能优化

| 解决方案 | 效果 |

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

| dynamic(() => import("./Apartment3D"), { ssr: false }) | Three.js懒加载,约150 KB gzip |

| antialias: false | 减少15-20%填充率 |

| dpr: Math.min(devicePixelRatio, 2) | 在高DPI设备上节省2-4倍资源 |

| NearestFilter | 无三线性过滤 |

| castShadow 仅用于家具 | 选择性阴影投射 |

| receiveShadow 仅用于地板 | 单一接收器 |

3D包:约600 KB原始大小,经过Tree-shaken的Three.js。

游戏化与未来计划

  • 经济系统:发帖赚取金币,每日上限100。
  • 等级系统:10级,最高3500 XP。
  • 建筑成长:基于内容,1-6层。
  • 活动:每年8次,XP三倍。
  • 秘密房间:10个,通过代码进入。

下一步:AI生成家具、奖杯、实例化/LOD。

关键要点

  • 使用4-15个基本几何体程序化生成家具,无需下载glTF文件。
  • Socket.io配合volatile.emit和15%线性插值确保流畅多人联机。
  • CanvasTexture + NearestFilter实现像素风格内容和角色。
  • 优化将包大小降至150 KB gzip,支持300-800个对象。
  • 声明式模板允许5种公寓变体,无需修改代码。

— Editorial Team

Advertisement 728x90

继续阅读