我的房间 - 极致精细 3D 数字孪生
by EpicCoder88676 lines33.9 KB🛠️ Three.js (3D graphics)
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>我的房间 - 极致精细 3D 数字孪生</title>
<style>
body { margin: 0; overflow: hidden; background-color: #1a1a1a; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; }
canvas { display: block; }
#info {
position: absolute; bottom: 20px; left: 20px;
color: rgba(255,255,255,0.85); font-size: 13px;
background: rgba(0,0,0,0.6); padding: 12px 24px; border-radius: 12px;
pointer-events: none; line-height: 1.6; backdrop-filter: blur(5px);
border: 1px solid rgba(255,255,255,0.1);
}
</style>
</head>
<body>
<div id="info">
🖱️ 鼠标左键旋转 | 滚轮缩放 | 右键平移<br>
✨ 极致精细版:加入动态纹理、复杂光影与生活杂物<br>
💡 提示:靠近床头柜,看看红色台灯发出的真实暖光
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/controls/OrbitControls.js"></script>
<script>
// ==========================================
// 1. 初始化场景与渲染器
// ==========================================
const scene = new THREE.Scene();
scene.background = new THREE.Color(0xeef2f5);
scene.fog = new THREE.Fog(0xeef2f5, 30, 80); // 增加景深感
const camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 100);
camera.position.set(14, 12, 26);
camera.lookAt(0, 5, 0);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.toneMappingExposure = 1.0;
renderer.outputEncoding = THREE.sRGBEncoding;
document.body.appendChild(renderer.domElement);
const controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.target.set(0, 5, 0);
controls.enableDamping = true;
controls.dampingFactor = 0.05;
controls.maxPolarAngle = Math.PI / 2 + 0.1; // 允许稍微看到一点地板边缘
// ==========================================
// 2. 精细光照系统 (自然光 + 台灯暖光)
// ==========================================
// 环境光
scene.add(new THREE.AmbientLight(0xffffff, 0.4));
// 半球光模拟漫反射
const hemiLight = new THREE.HemisphereLight(0xffffff, 0x444444, 0.4);
hemiLight.position.set(0, 20, 0);
scene.add(hemiLight);
// 主平行光 (模拟窗外阳光)
const dirLight = new THREE.DirectionalLight(0xfff8e7, 1.2);
dirLight.position.set(20, 25, 15);
dirLight.castShadow = true;
dirLight.shadow.mapSize.width = 2048;
dirLight.shadow.mapSize.height = 2048;
dirLight.shadow.camera.near = 1;
dirLight.shadow.camera.far = 60;
dirLight.shadow.camera.left = -25;
dirLight.shadow.camera.right = 25;
dirLight.shadow.camera.top = 25;
dirLight.shadow.camera.bottom = -25;
dirLight.shadow.bias = -0.0005;
scene.add(dirLight);
// 床头柜红色台灯的暖黄点光源 (精细氛围)
const lampLight = new THREE.PointLight(0xffaa55, 1.5, 8, 2);
lampLight.position.set(11.5, 5, 9.5);
lampLight.castShadow = true;
lampLight.shadow.mapSize.width = 1024;
lampLight.shadow.mapSize.height = 1024;
scene.add(lampLight);
// ==========================================
// 3. 高级材质纹理生成器 (Canvas)
// ==========================================
// 木纹生成
function createWoodTexture() {
const canvas = document.createElement('canvas');
canvas.width = 512; canvas.height = 512;
const ctx = canvas.getContext('2d');
ctx.fillStyle = '#8b5a2b'; ctx.fillRect(0, 0, 512, 512);
ctx.strokeStyle = '#5c3a1a'; ctx.lineWidth = 2;
for(let i=0; i<50; i++) {
ctx.beginPath();
ctx.moveTo(0, Math.random() * 512);
ctx.bezierCurveTo(128, Math.random() * 512, 384, Math.random() * 512, 512, Math.random() * 512);
ctx.stroke();
}
const tex = new THREE.CanvasTexture(canvas);
tex.wrapS = THREE.RepeatWrapping; tex.wrapT = THREE.RepeatWrapping;
return tex;
}
// 凉席纹理生成
function createMatTexture() {
const canvas = document.createElement('canvas');
canvas.width = 256; canvas.height = 256;
const ctx = canvas.getContext('2d');
ctx.fillStyle = '#a05a38'; ctx.fillRect(0, 0, 256, 256);
ctx.strokeStyle = '#7a4225'; ctx.lineWidth = 1;
for (let i = 0; i <= 256; i += 10) {
ctx.beginPath(); ctx.moveTo(i, 0); ctx.lineTo(i, 256); ctx.stroke();
ctx.beginPath(); ctx.moveTo(0, i); ctx.lineTo(256, i); ctx.stroke();
}
const tex = new THREE.CanvasTexture(canvas);
tex.wrapS = THREE.RepeatWrapping; tex.wrapT = THREE.RepeatWrapping;
tex.repeat.set(6, 4);
return tex;
}
// 墙面水渍纹理
function createStainTexture() {
const canvas = document.createElement('canvas');
canvas.width = 512; canvas.height = 512;
const ctx = canvas.getContext('2d');
ctx.clearRect(0, 0, 512, 512);
// 多层模糊模拟水渍渗透
ctx.fillStyle = 'rgba(160, 130, 90, 0.3)';
for(let i=0; i<30; i++) {
ctx.beginPath();
ctx.arc(256 + Math.random()*200 - 100, 256 + Math.random()*200 - 100, 30 + Math.random()*80, 0, Math.PI*2);
ctx.fill();
}
return new THREE.CanvasTexture(canvas);
}
// 蝴蝶墙贴
function createButterflyTexture() {
const canvas = document.createElement('canvas');
canvas.width = 512; canvas.height = 256;
const ctx = canvas.getContext('2d');
ctx.clearRect(0, 0, 512, 256);
const colors = ['#ff6b6b', '#4ecdc4', '#ffe66d', '#a06cd5', '#ff9f43'];
for(let i=0; i<8; i++) {
const x = 50 + i * 60;
const y = 50 + Math.random() * 150;
ctx.fillStyle = colors[i % colors.length];
// 简笔蝴蝶
ctx.beginPath(); ctx.ellipse(x, y, 12, 25, Math.PI/4, 0, Math.PI*2); ctx.fill();
ctx.beginPath(); ctx.ellipse(x, y, 12, 25, -Math.PI/4, 0, Math.PI*2); ctx.fill();
}
return new THREE.CanvasTexture(canvas);
}
const woodTexture = createWoodTexture();
const matTexture = createMatTexture();
const stainTexture = createStainTexture();
const butterflyTexture = createButterflyTexture();
// 通用材质工具
const getMat = (color, roughness = 0.8, metalness = 0.0, map = null) =>
new THREE.MeshStandardMaterial({ color, roughness, metalness, map });
// 木纹材质
const woodMat = getMat(0x8b5a2b, 0.7, 0.0, woodTexture);
const woodDarkMat = getMat(0x654321, 0.8, 0.0, woodTexture);
// 磨砂玻璃材质 (衣柜门)
const frostedGlassMat = new THREE.MeshPhysicalMaterial({
color: 0xcce0cc, transparent: true, opacity: 0.5, roughness: 0.2, transmission: 0.9
});
// ==========================================
// 4. 空间结构 (地板、墙壁、天花板、地脚线)
// ==========================================
// 地板
const floor = new THREE.Mesh(new THREE.PlaneGeometry(40, 40), getMat(0xdcdfe3, 0.6, 0.1));
floor.rotation.x = -Math.PI / 2; floor.receiveShadow = true; scene.add(floor);
// 墙壁
const wallMat = getMat(0xfdfaf7, 0.9);
const wallThickness = 0.5;
const roomW = 32, roomD = 32, roomH = 16;
// 后墙 (z = -16)
const backWall = new THREE.Mesh(new THREE.BoxGeometry(roomW, roomH, wallThickness), wallMat);
backWall.position.set(0, roomH/2, -roomD/2); backWall.receiveShadow = true; scene.add(backWall);
// 左墙 (x = -16)
const leftWall = new THREE.Mesh(new THREE.BoxGeometry(wallThickness, roomH, roomD), wallMat);
leftWall.position.set(-roomW/2, roomH/2, 0); leftWall.receiveShadow = true; scene.add(leftWall);
// 右墙 (x = 16)
const rightWall = new THREE.Mesh(new THREE.BoxGeometry(wallThickness, roomH, roomD), wallMat);
rightWall.position.set(roomW/2, roomH/2, 0); rightWall.receiveShadow = true; scene.add(rightWall);
// 天花板
const ceiling = new THREE.Mesh(new THREE.PlaneGeometry(roomW, roomD), getMat(0xffffff, 1.0));
ceiling.rotation.x = Math.PI / 2; ceiling.position.y = roomH; scene.add(ceiling);
// 墙面水渍
const stainMat = new THREE.MeshStandardMaterial({ map: stainTexture, transparent: true, opacity: 0.6, depthWrite: false });
const stainBack = new THREE.Mesh(new THREE.PlaneGeometry(10, 8), stainMat);
stainBack.position.set(8, 4, -15.7); stainBack.rotation.y = Math.PI; scene.add(stainBack);
const stainRight = new THREE.Mesh(new THREE.PlaneGeometry(12, 10), stainMat);
stainRight.position.set(15.7, 5, 2); stainRight.rotation.y = -Math.PI / 2; scene.add(stainRight);
// 地脚线
const baseboardMat = getMat(0x333333, 0.8);
const bbBack = new THREE.Mesh(new THREE.BoxGeometry(roomW, 0.6, 0.2), baseboardMat);
bbBack.position.set(0, 0.3, -15.9); scene.add(bbBack);
const bbRight = new THREE.Mesh(new THREE.BoxGeometry(0.2, 0.6, roomD), baseboardMat);
bbRight.position.set(15.9, 0.3, 0); scene.add(bbRight);
// 墙面插座
const socket = new THREE.Mesh(new THREE.BoxGeometry(0.8, 0.8, 0.1), getMat(0xffffff, 0.3));
socket.position.set(10, 4, -15.7); scene.add(socket);
// ==========================================
// 5. 极致细节家具:左侧巨无霸衣柜
// ==========================================
const wardrobeGroup = new THREE.Group();
const wW = 9, wH = 13, wD = 4.5;
// 柜体框架
const wBody = new THREE.Mesh(new THREE.BoxGeometry(wW, wH, wD), woodMat);
wBody.position.y = wH/2; wBody.castShadow = true; wBody.receiveShadow = true;
wardrobeGroup.add(wBody);
// 顶柜
const topCabinet = new THREE.Mesh(new THREE.BoxGeometry(wW, 3.5, wD), woodMat);
topCabinet.position.y = wH - 1.75; topCabinet.castShadow = true; wardrobeGroup.add(topCabinet);
// 顶柜把手
for(let i = -3; i <= 3; i+=6) {
const handle = new THREE.Mesh(new THREE.BoxGeometry(0.1, 1.2, 0.1), getMat(0xcccccc, 0.2, 0.8));
handle.position.set(i, wH - 1.75, wD/2 + 0.05);
wardrobeGroup.add(handle);
}
// 内部隔板
for(let y = 3; y <= 9; y += 3) {
const shelf = new THREE.Mesh(new THREE.BoxGeometry(wW - 0.4, 0.2, wD - 0.4), woodDarkMat);
shelf.position.set(0, y, 0); wardrobeGroup.add(shelf);
}
const vertShelf = new THREE.Mesh(new THREE.BoxGeometry(0.2, wH, wD - 0.4), woodDarkMat);
vertShelf.position.set(1.5, wH/2, 0); wardrobeGroup.add(vertShelf);
// 挂满衣服 (加入衣架和斜度)
const clothesColors = [0xffb6c1, 0xadd8e6, 0xffffff, 0xf5f5dc, 0x87ceeb, 0xffe4b5, 0xd3d3d3, 0xffdead, 0x9370db];
for(let i=0; i<50; i++) {
const clothMat = getMat(clothesColors[Math.floor(Math.random() * clothesColors.length)], 0.95);
const clothH = 1.5 + Math.random() * 2.5;
const clothW = 0.6 + Math.random() * 0.5;
const cloth = new THREE.Mesh(new THREE.BoxGeometry(clothW, clothH, 0.15), clothMat);
// 衣服位置
const shelfY = Math.floor(i / 12) * 3 + 3;
const posX = (Math.random() - 0.5) * 4 + 0.5;
const posZ = (Math.random() - 0.5) * 2;
cloth.position.set(posX, shelfY + 0.8, posZ);
cloth.rotation.y = (Math.random() - 0.5) * 0.8;
cloth.rotation.z = (Math.random() - 0.5) * 0.1;
cloth.castShadow = true;
wardrobeGroup.add(cloth);
// 加上衣架 (金属细杆)
const hanger = new THREE.Mesh(new THREE.BoxGeometry(clothW * 0.8, 0.05, 0.05), getMat(0xcccccc, 0.3, 0.8));
hanger.position.set(posX, shelfY + 0.8 + clothH/2, posZ);
hanger.rotation.y = cloth.rotation.y;
wardrobeGroup.add(hanger);
}
// 推拉门 (带金属边框)
const doorW = wW / 2;
const doorLeft = new THREE.Mesh(new THREE.BoxGeometry(doorW, wH - 1, 0.2), frostedGlassMat);
doorLeft.position.set(-doorW/2 + 0.1, (wH-1)/2, wD/2);
wardrobeGroup.add(doorLeft);
const doorRight = new THREE.Mesh(new THREE.BoxGeometry(doorW, wH - 1, 0.2), frostedGlassMat);
doorRight.position.set(doorW/2 - 0.1, (wH-1)/2, wD/2);
wardrobeGroup.add(doorRight);
// 门框边缘
const frameMat = getMat(0xaaaaaa, 0.3, 0.8);
const doorFrames = [
{w: 0.15, h: wH-1, d: 0.3, x: -wW/2+0.15, y: (wH-1)/2},
{w: 0.15, h: wH-1, d: 0.3, x: wW/2-0.15, y: (wH-1)/2},
{w: wW, h: 0.15, d: 0.3, x: 0, y: 0.15}
];
doorFrames.forEach(f => {
const frame = new THREE.Mesh(new THREE.BoxGeometry(f.w, f.h, f.d), frameMat);
frame.position.set(f.x, f.y, wD/2); wardrobeGroup.add(frame);
});
wardrobeGroup.position.set(-12, 0, -8);
scene.add(wardrobeGroup);
// ==========================================
// 6. 飘窗与窗户系统 (右侧深化)
// ==========================================
const winX = 15.7, winZ = 2;
// 飘窗台面
const sill = new THREE.Mesh(new THREE.BoxGeometry(3.5, 0.5, 13), getMat(0xffffff, 0.7));
sill.position.set(winX - 1.75, 2, winZ); sill.receiveShadow = true; scene.add(sill);
// 绿色毛绒垫 (加厚)
const rug = new THREE.Mesh(new THREE.BoxGeometry(3.3, 0.2, 12.5), getMat(0x7ec850, 1.0));
rug.position.set(winX - 1.75, 2.35, winZ); rug.receiveShadow = true; scene.add(rug);
// 窗户框架 (绿色)
const frameMatGreen = getMat(0x2e8b57, 0.6);
const winFrameTop = new THREE.Mesh(new THREE.BoxGeometry(0.2, 0.3, 13), frameMatGreen);
winFrameTop.position.set(winX, 8.5, winZ); scene.add(winFrameTop);
const winFrameBottom = new THREE.Mesh(new THREE.BoxGeometry(0.2, 0.3, 13), frameMatGreen);
winFrameBottom.position.set(winX, 2.5, winZ); scene.add(winFrameBottom);
// 竖向窗格
for(let i = -6; i <= 6; i+=3) {
const bar = new THREE.Mesh(new THREE.BoxGeometry(0.2, 6, 0.2), frameMatGreen);
bar.position.set(winX, 5.5, winZ + i); scene.add(bar);
}
// 横向窗格
for(let i = 3.5; i <= 7.5; i+=2) {
const bar = new THREE.Mesh(new THREE.BoxGeometry(0.2, 0.2, 13), frameMatGreen);
bar.position.set(winX, i, winZ); scene.add(bar);
}
// 玻璃
const glass = new THREE.Mesh(new THREE.PlaneGeometry(12.5, 6), new THREE.MeshPhysicalMaterial({ color: 0xaaccff, transparent: true, opacity: 0.1, roughness: 0.05, transmission: 0.95 }));
glass.position.set(winX, 5.5, winZ); glass.rotation.y = -Math.PI / 2; scene.add(glass);
// 防盗网
const barMat = getMat(0xaaaaaa, 0.5, 0.8);
for(let i = -6; i <= 6; i+=0.8) {
const bar = new THREE.Mesh(new THREE.CylinderGeometry(0.03, 0.03, 6, 8), barMat);
bar.position.set(winX - 0.15, 5.5, winZ + i); scene.add(bar);
}
// 飘窗上的衣物堆叠 (更逼真的褶皱)
const quiltMat = getMat(0xbbccee, 0.95);
const quilt1 = new THREE.Mesh(new THREE.BoxGeometry(2.2, 1.2, 4.5), quiltMat);
quilt1.position.set(winX - 1.5, 3.1, winZ - 1.5); quilt1.rotation.set(0.1, 0.2, 0.1); quilt1.castShadow = true; scene.add(quilt1);
const quilt2 = new THREE.Mesh(new THREE.BoxGeometry(2, 0.8, 3.5), getMat(0xe0e0e0, 0.95));
quilt2.position.set(winX - 1.7, 2.9, winZ - 1.8); quilt2.rotation.set(-0.1, -0.3, 0.05); quilt2.castShadow = true; scene.add(quilt2);
const pillow = new THREE.Mesh(new THREE.BoxGeometry(2, 0.9, 1.8), getMat(0xffee88, 0.95));
pillow.position.set(winX - 1.5, 3.1, winZ + 4.5); pillow.rotation.set(0.1, 0.5, 0.1); pillow.castShadow = true; scene.add(pillow);
// 窗帘帷幔 (带波浪形)
const valanceMat = getMat(0xfdfaf7, 0.9);
const valance = new THREE.Mesh(new THREE.BoxGeometry(0.6, 2.5, 13), valanceMat);
valance.position.set(winX - 0.8, 11.5, winZ); scene.add(valance);
const curtain = new THREE.Mesh(new THREE.PlaneGeometry(3.5, 13, 30, 30), valanceMat);
const posAttr = curtain.geometry.attributes.position;
for (let i = 0; i < posAttr.count; i++) {
const y = posAttr.getY(i);
posAttr.setZ(i, Math.sin(y * 2.5) * 0.4 + Math.cos(y * 1.5) * 0.2);
}
curtain.geometry.computeVertexNormals();
curtain.position.set(winX - 1.2, 7.5, winZ - 5.5);
curtain.rotation.y = -Math.PI / 2;
scene.add(curtain);
// ==========================================
// 7. 书桌、电脑与办公区 (精细建模)
// ==========================================
const deskGroup = new THREE.Group();
// 桌面 (加厚带边缘)
const deskTop = new THREE.Mesh(new THREE.BoxGeometry(7, 0.3, 3), woodMat);
deskTop.castShadow = true; deskTop.receiveShadow = true;
deskGroup.add(deskTop);
// 抽屉柜
const drawerBody = new THREE.Mesh(new THREE.BoxGeometry(2.5, 4, 2.8), woodDarkMat);
drawerBody.position.set(-2.5, -2.15, 0); drawerBody.castShadow = true; drawerBody.receiveShadow = true;
deskGroup.add(drawerBody);
// 抽屉把手
for(let y = -1; y >= -3; y-=1.5) {
const handle = new THREE.Mesh(new THREE.BoxGeometry(1, 0.08, 0.08), getMat(0xcccccc, 0.2, 0.8));
handle.position.set(-2.5, y, 1.45); deskGroup.add(handle);
}
// 显示器 (带弧面背板)
const monitorBase = new THREE.Mesh(new THREE.CylinderGeometry(0.6, 0.8, 0.2, 16), getMat(0x222222, 0.4));
monitorBase.position.set(2, 0.25, 0); deskGroup.add(monitorBase);
const monitorStand = new THREE.Mesh(new THREE.CylinderGeometry(0.08, 0.08, 1.2, 8), getMat(0x333333, 0.4));
monitorStand.position.set(2, 0.8, 0); deskGroup.add(monitorStand);
const screen = new THREE.Mesh(new THREE.BoxGeometry(2.5, 1.6, 0.1), getMat(0x111111, 0.2));
screen.position.set(2, 1.8, 0); screen.castShadow = true; deskGroup.add(screen);
// 屏幕反光面
const screenFace = new THREE.Mesh(new THREE.PlaneGeometry(2.3, 1.4), getMat(0x2a2a2a, 0.1, 0.5));
screenFace.position.set(2, 1.8, 0.06); deskGroup.add(screenFace);
// 键盘和鼠标
const keyboard = new THREE.Mesh(new THREE.BoxGeometry(1.8, 0.1, 0.6), getMat(0xdddddd, 0.5));
keyboard.position.set(1.2, 0.2, 0.8); deskGroup.add(keyboard);
// 键帽
for(let kx=-0.7; kx<=0.7; kx+=0.15) {
for(let kz=-0.2; kz<=0.2; kz+=0.15) {
const key = new THREE.Mesh(new THREE.BoxGeometry(0.1, 0.05, 0.1), getMat(0x333333, 0.5));
key.position.set(1.2+kx, 0.27, 0.8+kz); deskGroup.add(key);
}
}
const mouse = new THREE.Mesh(new THREE.SphereGeometry(0.2, 16, 16), getMat(0x333333, 0.3));
mouse.scale.set(1, 0.6, 1.3); mouse.position.set(2.5, 0.25, 0.8); deskGroup.add(mouse);
// 电脑主机
const pcTower = new THREE.Mesh(new THREE.BoxGeometry(1.2, 2.8, 2), getMat(0x1a1a1a, 0.3, 0.5));
pcTower.position.set(3, -1.4, 0.5); pcTower.castShadow = true; deskGroup.add(pcTower);
// 桌面上杂乱的物品 (笔筒、书本、水杯)
const book = new THREE.Mesh(new THREE.BoxGeometry(1.5, 0.2, 1), getMat(0xaa4444, 0.8));
book.position.set(-1, 0.25, -0.5); deskGroup.add(book);
const cup = new THREE.Mesh(new THREE.CylinderGeometry(0.2, 0.2, 0.5, 16), getMat(0xffffff, 0.5));
cup.position.set(0.5, 0.4, -0.8); deskGroup.add(cup);
// 小盆栽
const pot = new THREE.Mesh(new THREE.CylinderGeometry(0.25, 0.2, 0.3, 12), getMat(0x8b4513, 0.8));
pot.position.set(-1.5, 0.3, -0.8); deskGroup.add(pot);
const plant = new THREE.Mesh(new THREE.SphereGeometry(0.3, 8, 8), getMat(0x2e8b57, 0.9));
plant.position.set(-1.5, 0.6, -0.8); deskGroup.add(plant);
deskGroup.position.set(9, 2.5, -13);
scene.add(deskGroup);
// ==========================================
// 8. 粉色转椅 (平滑曲面 + 滚轮细化)
// ==========================================
const chairGroup = new THREE.Group();
const pinkMat = getMat(0xf4b6c2, 0.6);
// 靠背 (使用圆柱体切割,更加圆润)
const chairBackGeo = new THREE.CylinderGeometry(1.2, 1.2, 2.2, 32, 1, false, 0, Math.PI);
const chairBack = new THREE.Mesh(chairBackGeo, pinkMat);
chairBack.rotation.y = Math.PI;
chairBack.position.set(0, 1.2, -0.8);
chairBack.scale.set(1, 1, 0.7); // 压扁一些
chairBack.castShadow = true;
chairGroup.add(chairBack);
// 坐垫 (带边缘弧度)
const cushionGeo = new THREE.BoxGeometry(2, 0.5, 2);
const cushion = new THREE.Mesh(cushionGeo, pinkMat);
cushion.position.set(0, 0, 0); cushion.castShadow = true;
chairGroup.add(cushion);
// 坐垫边缘修饰
const cushionEdge = new THREE.Mesh(new THREE.BoxGeometry(2.1, 0.1, 2.1), getMat(0xe8a2b0, 0.6));
cushionEdge.position.set(0, -0.2, 0); chairGroup.add(cushionEdge);
// 气压杆
const pole = new THREE.Mesh(new THREE.CylinderGeometry(0.15, 0.15, 1.5, 12), getMat(0xcccccc, 0.2, 0.8));
pole.position.set(0, -0.8, 0); chairGroup.add(pole);
// 五星脚底座
const base = new THREE.Mesh(new THREE.CylinderGeometry(1.4, 1.4, 0.1, 32), getMat(0xffffff, 0.3));
base.position.set(0, -1.5, 0); base.castShadow = true; chairGroup.add(base);
// 滚轮 (细化每个轮的连接件)
for(let i=0; i<5; i++) {
const angle = (i / 5) * Math.PI * 2;
const wheelArm = new THREE.Mesh(new THREE.BoxGeometry(0.6, 0.1, 0.1), getMat(0xffffff, 0.3));
wheelArm.position.set(Math.cos(angle) * 0.7, -1.55, Math.sin(angle) * 0.7);
wheelArm.rotation.y = -angle;
chairGroup.add(wheelArm);
const wheel = new THREE.Mesh(new THREE.CylinderGeometry(0.12, 0.12, 0.08, 16), getMat(0x333333, 0.5));
wheel.rotation.z = Math.PI / 2;
wheel.rotation.y = angle;
wheel.position.set(Math.cos(angle) * 1.3, -1.6, Math.sin(angle) * 1.3);
wheel.castShadow = true;
chairGroup.add(wheel);
}
chairGroup.position.set(6.5, 2.5, -10);
chairGroup.rotation.y = -0.2; // 稍微偏向书桌
scene.add(chairGroup);
// ==========================================
// 9. 空调、风扇与地面小物件
// ==========================================
// 空调
const acGroup = new THREE.Group();
const acBody = new THREE.Mesh(new THREE.BoxGeometry(4.5, 1.3, 0.8), getMat(0xffffff, 0.4));
acBody.castShadow = true; acGroup.add(acBody);
const acVent = new THREE.Mesh(new THREE.BoxGeometry(4, 0.2, 0.1), getMat(0xcccccc, 0.2));
acVent.position.set(0, -0.5, 0.4); acGroup.add(acVent);
// 指示灯
const acLight = new THREE.Mesh(new THREE.SphereGeometry(0.05, 8, 8), getMat(0x00ff00, 0.2));
acLight.position.set(1.5, 0.3, 0.45); acGroup.add(acLight);
acGroup.position.set(2, 12, -15.7); scene.add(acGroup);
// 风扇 (网罩细化)
const fanGroup = new THREE.Group();
const fanBase = new THREE.Mesh(new THREE.CylinderGeometry(1.3, 1.3, 0.2, 32), getMat(0xeeeeee, 0.4));
fanBase.position.y = 0.1; fanBase.castShadow = true; fanGroup.add(fanBase);
const fanPole = new THREE.Mesh(new THREE.CylinderGeometry(0.1, 0.1, 4.5, 16), getMat(0xeeeeee, 0.4));
fanPole.position.y = 2.25; fanPole.castShadow = true; fanGroup.add(fanPole);
// 风扇头外壳
const fanHead = new THREE.Mesh(new THREE.CylinderGeometry(1.5, 1.5, 0.7, 32), getMat(0xdddddd, 0.4));
fanHead.position.y = 5; fanHead.rotation.x = Math.PI / 2; fanHead.castShadow = true; fanGroup.add(fanHead);
// 立体网罩 (多个圆环)
const ringMat = new THREE.MeshBasicMaterial({ color: 0x555555 });
for(let r = 0.3; r <= 1.5; r+=0.3) {
const ring = new THREE.Mesh(new THREE.TorusGeometry(r, 0.02, 8, 40), ringMat);
ring.position.y = 5; ring.position.z = 0.35; // 在外壳前面一点
ring.rotation.x = Math.PI / 2;
fanGroup.add(ring);
}
// 中心电机
const motor = new THREE.Mesh(new THREE.SphereGeometry(0.4, 16, 16), getMat(0x555555, 0.3));
motor.position.set(0, 5, 0.4); fanGroup.add(motor);
// 扇叶
for(let i=0; i<4; i++) {
const blade = new THREE.Mesh(new THREE.BoxGeometry(0.15, 1.2, 0.05), getMat(0xdddddd, 0.4));
blade.position.y = 5; blade.position.z = 0.3;
blade.rotation.z = (i / 4) * Math.PI * 2;
fanGroup.add(blade);
}
fanGroup.position.set(-6, 0, -6);
scene.add(fanGroup);
// 地面杂物:蓝色呼啦圈
const ringGeo = new THREE.TorusGeometry(1.2, 0.08, 16, 40);
const ring = new THREE.Mesh(ringGeo, getMat(0x4a90e2, 0.5));
ring.position.set(-10, 0.1, -1.5); ring.rotation.x = -Math.PI / 2; ring.castShadow = true;
scene.add(ring);
// 绿色收纳盒
const greenBox = new THREE.Mesh(new THREE.BoxGeometry(1, 1.5, 0.8), getMat(0x2e8b57, 0.8));
greenBox.position.set(-14, 0.75, -3); greenBox.castShadow = true; scene.add(greenBox);
const greenBox2 = greenBox.clone(); greenBox2.position.set(-12.8, 0.75, -3); scene.add(greenBox2);
// 垃圾桶
const trash = new THREE.Mesh(new THREE.CylinderGeometry(0.8, 0.6, 1.6, 16), getMat(0x8fa3ad, 0.7));
trash.position.set(14, 0.8, -5); trash.castShadow = true; scene.add(trash);
// 垃圾袋边缘
const trashBag = new THREE.Mesh(new THREE.CylinderGeometry(0.85, 0.85, 0.1, 16), getMat(0x9b59b6, 0.9));
trashBag.position.set(14, 1.6, -5); scene.add(trashBag);
// ==========================================
// 10. 床铺、床头细节与台灯 (核心生活区)
// ==========================================
// 床架
const bedMat = new THREE.MeshStandardMaterial({ color: 0x9e5a38, roughness: 0.9, map: matTexture });
const bedBase = new THREE.Mesh(new THREE.BoxGeometry(24, 0.8, 14), bedMat);
bedBase.position.set(-2, 0.4, 15);
bedBase.receiveShadow = true; scene.add(bedBase);
// 床铺白边
const bedEdgeMat = getMat(0xd3d3d3, 0.8);
const bedFrontEdge = new THREE.Mesh(new THREE.BoxGeometry(24.2, 0.2, 0.5), bedEdgeMat);
bedFrontEdge.position.set(-2, 0.9, 22.25); scene.add(bedFrontEdge);
const bedLeftEdge = new THREE.Mesh(new THREE.BoxGeometry(0.5, 0.2, 14), bedEdgeMat);
bedLeftEdge.position.set(-14.1, 0.9, 15); scene.add(bedLeftEdge);
// 白色床头板
const headboard = new THREE.Mesh(new THREE.BoxGeometry(20, 4, 0.6), getMat(0xffffff, 0.6));
headboard.position.set(-2, 2.5, 21.7); headboard.receiveShadow = true; scene.add(headboard);
// 蝴蝶墙贴
const butterflyMat = new THREE.MeshStandardMaterial({ map: butterflyTexture, transparent: true, depthWrite: false });
const butterflyPlane = new THREE.Mesh(new THREE.PlaneGeometry(16, 5), butterflyMat);
butterflyPlane.position.set(-2, 6.5, 21.5);
butterflyPlane.rotation.y = Math.PI;
scene.add(butterflyPlane);
// 床头柜与抽屉
const nightstand = new THREE.Mesh(new THREE.BoxGeometry(3, 4, 3), getMat(0xf0f0f0, 0.8));
nightstand.position.set(12, 2, 10);
nightstand.castShadow = true; nightstand.receiveShadow = true;
scene.add(nightstand);
// 抽屉拉出
const drawerFront = new THREE.Mesh(new THREE.BoxGeometry(2.8, 1.5, 0.2), getMat(0xe0e0e0, 0.8));
drawerFront.position.set(12, 1.2, 11.5);
scene.add(drawerFront);
// 抽屉里的物品
const tissueBox = new THREE.Mesh(new THREE.BoxGeometry(1, 0.6, 0.5), getMat(0xd8bfd8, 0.9));
tissueBox.position.set(11.5, 1.5, 10.8); scene.add(tissueBox);
const tissuePack = new THREE.Mesh(new THREE.BoxGeometry(0.6, 0.4, 0.4), getMat(0xadd8e6, 0.9));
tissuePack.position.set(12.5, 1.4, 11.1); scene.add(tissuePack);
// 红色折叠台灯 (极其精细)
const lampGroup = new THREE.Group();
const lampBase = new THREE.Mesh(new THREE.CylinderGeometry(0.8, 0.8, 0.25, 16), getMat(0xff3333, 0.4));
lampBase.position.y = 0.125; lampGroup.add(lampBase);
// 底座电源线
const cable = new THREE.Mesh(new THREE.TorusGeometry(0.5, 0.05, 8, 20, Math.PI), getMat(0x333333, 0.6));
cable.position.set(0, 0.05, 0); cable.rotation.x = Math.PI/2; lampGroup.add(cable);
// 机械臂 (关节)
const joint1 = new THREE.Mesh(new THREE.CylinderGeometry(0.15, 0.15, 0.4, 8), getMat(0x333333, 0.3));
joint1.position.set(0, 0.4, 0); lampGroup.add(joint1);
const arm1 = new THREE.Mesh(new THREE.BoxGeometry(0.1, 1.8, 0.1), getMat(0x333333, 0.3));
arm1.position.set(0, 1.3, 0); arm1.rotation.z = 0.3; lampGroup.add(arm1);
const joint2 = new THREE.Mesh(new THREE.CylinderGeometry(0.12, 0.12, 0.3, 8), getMat(0x333333, 0.3));
joint2.position.set(0.45, 2.2, 0); lampGroup.add(joint2);
const arm2 = new THREE.Mesh(new THREE.BoxGeometry(0.1, 1.8, 0.1), getMat(0x333333, 0.3));
arm2.position.set(0.65, 3.1, 0); arm2.rotation.z = -0.4; lampGroup.add(arm2);
// 灯罩 (锥形 + 开口)
const lampshade = new THREE.Mesh(new THREE.CylinderGeometry(0.2, 1.2, 1.8, 16, 1, true), getMat(0xff4444, 0.4));
lampshade.position.set(1.1, 4, 0); lampshade.rotation.z = -0.8; lampshade.material.side = THREE.DoubleSide;
lampGroup.add(lampshade);
// 灯泡发光
const bulb = new THREE.Mesh(new THREE.SphereGeometry(0.3, 16, 16), new THREE.MeshBasicMaterial({ color: 0xffaa55 }));
bulb.position.set(1.2, 3.8, 0); lampGroup.add(bulb);
lampGroup.position.set(11.5, 4, 9.5);
scene.add(lampGroup);
// 遥控器与纸巾盒 (床头柜上)
const remote = new THREE.Mesh(new THREE.BoxGeometry(0.9, 0.15, 0.3), getMat(0xffffff, 0.5));
remote.position.set(12.5, 4.1, 9.5); scene.add(remote);
// 灰色毛绒小熊 (床头)
const bearGroup = new THREE.Group();
const bearMat = getMat(0x999999, 0.95); // 灰色
const bearHead = new THREE.Mesh(new THREE.SphereGeometry(0.9, 16, 16), bearMat);
bearHead.position.y = 1.2; bearGroup.add(bearHead);
const bearBody = new THREE.Mesh(new THREE.CylinderGeometry(0.7, 0.9, 1.8, 16), bearMat);
bearBody.position.y = 0.2; bearGroup.add(bearBody);
// 耳朵
const earL = new THREE.Mesh(new THREE.SphereGeometry(0.35, 12, 12), bearMat); earL.position.set(-0.6, 1.8, 0); bearGroup.add(earL);
const earR = earL.clone(); earR.position.set(0.6, 1.8, 0); bearGroup.add(earR);
// 粉色眼罩
const eyeMaskMat = getMat(0xffb6c1, 0.9);
const eyeMask = new THREE.Mesh(new THREE.BoxGeometry(1.4, 0.4, 0.3), eyeMaskMat);
eyeMask.position.set(0, 1.3, 0.8); bearGroup.add(eyeMask);
bearGroup.position.set(-7, 1.5, 19);
bearGroup.rotation.y = 0.6;
bearGroup.scale.set(0.9, 0.9, 0.9);
scene.add(bearGroup);
// 床边矮柜
const fileCabinet = new THREE.Mesh(new THREE.BoxGeometry(2.5, 2.5, 2.5), getMat(0xffffff, 0.7));
fileCabinet.position.set(15, 1.25, 14); fileCabinet.castShadow = true; scene.add(fileCabinet);
// 床上抱枕
const bigPillow = new THREE.Mesh(new THREE.BoxGeometry(6, 4, 2), getMat(0xd3d3d3, 0.9));
bigPillow.position.set(-5, 2.5, 18); bigPillow.rotation.x = 0.15; bigPillow.rotation.y = 0.1;
bigPillow.castShadow = true; scene.add(bigPillow);
const patternPillow = new THREE.Mesh(new THREE.BoxGeometry(3.5, 1.2, 2.5), getMat(0xd8bfd8, 0.9));
patternPillow.position.set(-2, 1.6, 17.5); patternPillow.rotation.y = -0.3;
scene.add(patternPillow);
// ==========================================
// 11. 渲染循环与窗口适配
// ==========================================
function animate() {
requestAnimationFrame(animate);
controls.update();
renderer.render(scene, camera);
}
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
animate();
</script>
</body>
</html>Game Source: 我的房间 - 极致精细 3D 数字孪生
Creator: EpicCoder88
Libraries: three
Complexity: complex (676 lines, 33.9 KB)
The full source code is displayed above on this page.
Remix Instructions
To remix this game, copy the source code above and modify it. Add a ARCADELAB header at the top with "remix_of: 3d-epiccoder88-musdnmvo" to link back to the original. Then publish at arcadelab.ai/publish.