我的房间 - 终极数字孪生 3D 还原
by EpicCoder88691 lines35.5 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; top: 20px; left: 20px;
color: rgba(255,255,255,0.9); font-size: 14px;
background: rgba(0,0,0,0.7); padding: 15px 25px; border-radius: 10px;
pointer-events: none; line-height: 1.8; backdrop-filter: blur(10px);
border-left: 4px solid #4ecdc4;
}
#info b { color: #4ecdc4; }
</style>
</head>
<body>
<div id="info">
<b>🏠 数字孪生房间 - 极致细节版</b><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, 35, 80);
const camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 100);
camera.position.set(18, 14, 28);
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.1;
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.05;
// ==========================================
// 2. 高级光照系统 (模拟真实环境光)
// ==========================================
// 半球光 (天空色与地面色)
const hemiLight = new THREE.HemisphereLight(0xffffff, 0x444444, 0.6);
scene.add(hemiLight);
// 主光源 (阳光)
const dirLight = new THREE.DirectionalLight(0xfff5e6, 1.5);
dirLight.position.set(20, 25, 15);
dirLight.castShadow = true;
dirLight.shadow.mapSize.width = 4096;
dirLight.shadow.mapSize.height = 4096;
dirLight.shadow.camera.near = 1;
dirLight.shadow.camera.far = 80;
dirLight.shadow.camera.left = -30;
dirLight.shadow.camera.right = 30;
dirLight.shadow.camera.top = 30;
dirLight.shadow.camera.bottom = -30;
dirLight.shadow.bias = -0.0003;
dirLight.shadow.radius = 2;
scene.add(dirLight);
// 局部补光 (台灯发光)
const lampLight = new THREE.PointLight(0xffcc88, 0.8, 15);
lampLight.position.set(13, 6, 10);
scene.add(lampLight);
// ==========================================
// 3. 程序化纹理生成器 (CanvasTexture)
// ==========================================
// 生成木纹纹理
function createWoodTexture(baseColor, streakColor) {
const canvas = document.createElement('canvas'); canvas.width = 512; canvas.height = 512;
const ctx = canvas.getContext('2d');
ctx.fillStyle = baseColor; ctx.fillRect(0, 0, 512, 512);
ctx.strokeStyle = streakColor; ctx.lineWidth = 1;
for(let i=0; i<100; i++) {
ctx.beginPath();
let y = Math.random() * 512;
ctx.moveTo(0, y);
for(let x=0; x<512; x+=20) {
ctx.lineTo(x, y + Math.sin(x*0.05) * 5 + Math.random() * 3);
}
ctx.stroke();
}
const tex = new THREE.CanvasTexture(canvas);
tex.wrapS = THREE.RepeatWrapping; tex.wrapT = THREE.RepeatWrapping;
return tex;
}
// 生成瓷砖纹理
function createTileTexture() {
const canvas = document.createElement('canvas'); canvas.width = 512; canvas.height = 512;
const ctx = canvas.getContext('2d');
ctx.fillStyle = '#e8ebee'; ctx.fillRect(0, 0, 512, 512);
ctx.strokeStyle = '#b0b5ba'; ctx.lineWidth = 2;
// 绘制瓷砖缝隙
for(let i=0; i<=512; i+=128) {
ctx.beginPath(); ctx.moveTo(i, 0); ctx.lineTo(i, 512); ctx.stroke();
ctx.beginPath(); ctx.moveTo(0, i); ctx.lineTo(512, i); ctx.stroke();
}
const tex = new THREE.CanvasTexture(canvas);
tex.wrapS = THREE.RepeatWrapping; tex.wrapT = THREE.RepeatWrapping;
tex.repeat.set(4, 4);
return tex;
}
// 生成布料纹理(带噪点)
function createFabricTexture(baseColor) {
const canvas = document.createElement('canvas'); canvas.width = 256; canvas.height = 256;
const ctx = canvas.getContext('2d');
ctx.fillStyle = baseColor; ctx.fillRect(0, 0, 256, 256);
ctx.fillStyle = 'rgba(0,0,0,0.05)';
for(let i=0; i<2000; i++) {
ctx.fillRect(Math.random()*256, Math.random()*256, 2, 2);
}
const tex = new THREE.CanvasTexture(canvas);
tex.wrapS = THREE.RepeatWrapping; tex.wrapT = THREE.RepeatWrapping;
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);
// 多层半透明圆形叠加
for(let i=0; i<30; i++) {
ctx.fillStyle = `rgba(180, 150, 110, ${0.05 + Math.random()*0.1})`;
ctx.beginPath();
ctx.arc(256 + Math.random()*200 - 100, 256 + Math.random()*200 - 100, 50 + Math.random()*150, 0, Math.PI*2);
ctx.fill();
}
return new THREE.CanvasTexture(canvas);
}
// ==========================================
// 4. 材质库
// ==========================================
const woodTex = createWoodTexture('#8b5a2b', '#654321');
const darkWoodTex = createWoodTexture('#654321', '#4a3018');
const tileTex = createTileTexture();
const fabricGrayTex = createFabricTexture('#d3d3d3');
const fabricPinkTex = createFabricTexture('#f4b6c2');
const matWood = new THREE.MeshStandardMaterial({ map: woodTex, roughness: 0.7, metalness: 0.1 });
const matDarkWood = new THREE.MeshStandardMaterial({ map: darkWoodTex, roughness: 0.8, metalness: 0.0 });
const matTile = new THREE.MeshStandardMaterial({ map: tileTex, roughness: 0.4, metalness: 0.1 });
const matWall = new THREE.MeshStandardMaterial({ color: 0xfdfaf7, roughness: 0.95 });
const matMetal = new THREE.MeshStandardMaterial({ color: 0xcccccc, roughness: 0.2, metalness: 0.8 });
const matGlass = new THREE.MeshPhysicalMaterial({ color: 0xffffff, transparent: true, opacity: 0.2, roughness: 0.1, metalness: 0.1, transmission: 0.8 });
const matFabricGray = new THREE.MeshStandardMaterial({ map: fabricGrayTex, roughness: 0.95 });
const matFabricPink = new THREE.MeshStandardMaterial({ map: fabricPinkTex, roughness: 0.9 });
// ==========================================
// 5. 房间结构 (地、墙、踢脚线)
// ==========================================
const floor = new THREE.Mesh(new THREE.PlaneGeometry(36, 36), matTile);
floor.rotation.x = -Math.PI / 2; floor.receiveShadow = true; scene.add(floor);
const wallThickness = 0.4;
// 左墙
const leftWall = new THREE.Mesh(new THREE.BoxGeometry(wallThickness, 16, 36), matWall);
leftWall.position.set(-18, 8, 0); leftWall.receiveShadow = true; scene.add(leftWall);
// 后墙
const backWall = new THREE.Mesh(new THREE.BoxGeometry(36, 16, wallThickness), matWall);
backWall.position.set(0, 8, -18); backWall.receiveShadow = true; scene.add(backWall);
// 右墙
const rightWall = new THREE.Mesh(new THREE.BoxGeometry(wallThickness, 16, 36), matWall);
rightWall.position.set(18, 8, 0); rightWall.receiveShadow = true; scene.add(rightWall);
// 水渍 (后墙)
const stainMat = new THREE.MeshStandardMaterial({ map: createStainTexture(), transparent: true, opacity: 0.8, depthWrite: false });
const stain = new THREE.Mesh(new THREE.PlaneGeometry(10, 8), stainMat);
stain.position.set(8, 4, -17.7); stain.rotation.y = Math.PI; scene.add(stain);
// 踢脚线
const baseboardMat = new THREE.MeshStandardMaterial({ color: 0x333333, roughness: 0.8 });
const baseboardBack = new THREE.Mesh(new THREE.BoxGeometry(36, 0.6, 0.2), baseboardMat);
baseboardBack.position.set(0, 0.3, -17.9); scene.add(baseboardBack);
// 墙面开关和插座
const switchPlate = new THREE.Mesh(new THREE.BoxGeometry(0.4, 0.6, 0.05), matMetal);
switchPlate.position.set(-8, 5, -17.7); scene.add(switchPlate);
const socket = new THREE.Mesh(new THREE.BoxGeometry(0.6, 0.4, 0.05), matMetal);
socket.position.set(-4, 1, -17.7); scene.add(socket);
// ==========================================
// 6. 超精细衣柜 (带内部结构)
// ==========================================
const wardrobeGroup = new THREE.Group();
const wW = 10, wH = 14, wD = 4;
// 主柜体
const cabinetBody = new THREE.Mesh(new THREE.BoxGeometry(wW, wH, wD), matWood);
cabinetBody.position.set(0, wH/2, 0);
cabinetBody.castShadow = true; cabinetBody.receiveShadow = true;
wardrobeGroup.add(cabinetBody);
// 内部隔板
for(let y = 3; y <= 11; y += 2.5) {
const shelf = new THREE.Mesh(new THREE.BoxGeometry(wW - 0.4, 0.1, wD - 0.4), matDarkWood);
shelf.position.set(0, y, 0); wardrobeGroup.add(shelf);
}
// 竖向隔板
const vertShelf = new THREE.Mesh(new THREE.BoxGeometry(0.1, wH, wD - 0.4), matDarkWood);
vertShelf.position.set(1.5, wH/2, 0); wardrobeGroup.add(vertShelf);
// 顶柜
const topCabinet = new THREE.Mesh(new THREE.BoxGeometry(wW, 3, wD), matWood);
topCabinet.position.set(0, wH - 1.5, 0);
topCabinet.castShadow = true; topCabinet.receiveShadow = true;
wardrobeGroup.add(topCabinet);
// 抽屉把手 (金属)
for(let i = -3; i <= 3; i+=6) {
const handle = new THREE.Mesh(new THREE.CylinderGeometry(0.05, 0.05, 1.5, 8), matMetal);
handle.rotation.x = Math.PI / 2;
handle.position.set(i, wH - 1.5, wD/2 + 0.1);
wardrobeGroup.add(handle);
}
// 挂衣服 (用弯曲的平面和圆柱体精细模拟)
const clothesColors = [0xffb6c1, 0xadd8e6, 0xffffff, 0xf5f5dc, 0x87ceeb, 0xffe4b5, 0xd3d3d3, 0xffdead, 0x98fb98];
for(let i=0; i<60; i++) {
const clothMat = new THREE.MeshStandardMaterial({ color: clothesColors[Math.floor(Math.random() * clothesColors.length)], roughness: 0.9 });
const clothH = 2 + Math.random() * 3;
const clothW = 0.8 + Math.random() * 0.5;
// 使用弯曲的几何体模拟衣服下摆
const clothGeo = new THREE.PlaneGeometry(clothW, clothH, 4, 4);
const posAttr = clothGeo.attributes.position;
for(let j=0; j<posAttr.count; j++) {
const y = posAttr.getY(j);
posAttr.setZ(j, Math.sin(y * 2) * 0.1); // 衣摆波浪
}
clothGeo.computeVertexNormals();
const cloth = new THREE.Mesh(clothGeo, clothMat);
cloth.rotation.x = -Math.PI / 2;
// 随机挂在不同层
const level = Math.floor(i / 12) * 2.5 + 3;
const posX = (Math.random() - 0.5) * 4 + 1.5;
const posZ = (Math.random() - 0.5) * 2;
cloth.position.set(posX, level + 1.5, posZ);
cloth.rotation.y = (Math.random() - 0.5) * 0.8;
cloth.castShadow = true;
wardrobeGroup.add(cloth);
// 衣架勾
const hanger = new THREE.Mesh(new THREE.TorusGeometry(0.1, 0.02, 8, 16, Math.PI), matMetal);
hanger.rotation.z = Math.PI / 2;
hanger.position.set(posX, level + 3, posZ);
wardrobeGroup.add(hanger);
}
// 推拉门 (磨砂玻璃)
const doorMat = new THREE.MeshPhysicalMaterial({
color: 0xcce0cc, transparent: true, opacity: 0.5, roughness: 0.2, transmission: 0.6
});
const doorLeft = new THREE.Mesh(new THREE.BoxGeometry(5, 11, 0.2), doorMat);
doorLeft.position.set(-2.5, 5.5, wD/2); wardrobeGroup.add(doorLeft);
const doorRight = new THREE.Mesh(new THREE.BoxGeometry(5, 11, 0.2), doorMat);
doorRight.position.set(2.5, 5.5, wD/2); wardrobeGroup.add(doorRight);
// 门框
const frameL = new THREE.Mesh(new THREE.BoxGeometry(0.3, 11, 0.3), matMetal);
frameL.position.set(-5, 5.5, wD/2); wardrobeGroup.add(frameL);
const frameR = new THREE.Mesh(new THREE.BoxGeometry(0.3, 11, 0.3), matMetal);
frameR.position.set(5, 5.5, wD/2); wardrobeGroup.add(frameR);
const frameMid = new THREE.Mesh(new THREE.BoxGeometry(0.15, 11, 0.3), matMetal);
frameMid.position.set(0, 5.5, wD/2); wardrobeGroup.add(frameMid);
wardrobeGroup.position.set(-13, 0, -10);
wardrobeGroup.rotation.y = 0.1; // 稍微倾斜一点,显得自然
scene.add(wardrobeGroup);
// ==========================================
// 7. 飘窗与窗户 (深度细化)
// ==========================================
const windowX = 17.7, windowZ = 3;
// 窗台
const windowSill = new THREE.Mesh(new THREE.BoxGeometry(3.5, 0.6, 14), matWall);
windowSill.position.set(windowX - 1.75, 2.5, windowZ); windowSill.receiveShadow = true; scene.add(windowSill);
// 绿色毛绒垫
const rugMat = new THREE.MeshStandardMaterial({ color: 0x7ec850, roughness: 1.0 });
const rug = new THREE.Mesh(new THREE.BoxGeometry(3.2, 0.2, 13.5), rugMat);
rug.position.set(windowX - 1.75, 2.9, windowZ); rug.receiveShadow = true; scene.add(rug);
// 窗户框架 (绿色,带细节)
const frameMatGreen = new THREE.MeshStandardMaterial({ color: 0x2e8b57, roughness: 0.5 });
const winFrameTop = new THREE.Mesh(new THREE.BoxGeometry(0.3, 0.4, 14), frameMatGreen);
winFrameTop.position.set(windowX, 9, windowZ); scene.add(winFrameTop);
const winFrameBottom = new THREE.Mesh(new THREE.BoxGeometry(0.3, 0.4, 14), frameMatGreen);
winFrameBottom.position.set(windowX, 2.8, windowZ); scene.add(winFrameBottom);
// 窗格 (横竖交错)
for(let i = -6.5; i <= 6.5; i+=2.1) {
const bar = new THREE.Mesh(new THREE.BoxGeometry(0.3, 6.2, 0.2), frameMatGreen);
bar.position.set(windowX, 5.9, windowZ + i); scene.add(bar);
}
for(let i = 4; i <= 8; i+=2) {
const bar = new THREE.Mesh(new THREE.BoxGeometry(0.3, 0.2, 14), frameMatGreen);
bar.position.set(windowX, i, windowZ); scene.add(bar);
}
// 玻璃 (带一点反射)
const glass = new THREE.Mesh(new THREE.PlaneGeometry(13.8, 6), matGlass);
glass.position.set(windowX, 5.9, windowZ); glass.rotation.y = -Math.PI / 2; scene.add(glass);
// 防盗网 (细密网格)
const barMat = new THREE.MeshStandardMaterial({ color: 0xaaaaaa, roughness: 0.4, metalness: 0.6 });
for(let i = -6; i <= 6; i+=0.6) {
const bar = new THREE.Mesh(new THREE.CylinderGeometry(0.02, 0.02, 6, 6), barMat);
bar.position.set(windowX - 0.2, 5.9, windowZ + i); scene.add(bar);
}
for(let i = 3.5; i <= 8.5; i+=1) {
const bar = new THREE.Mesh(new THREE.CylinderGeometry(0.02, 0.02, 13.8, 6), barMat);
bar.rotation.z = Math.PI / 2;
bar.position.set(windowX - 0.2, i, windowZ); scene.add(bar);
}
// 飘窗上堆叠的被子 (使用顶点变形模拟褶皱)
const quiltGeo = new THREE.BoxGeometry(2.5, 1.5, 5, 10, 10, 10);
const quiltMat = new THREE.MeshStandardMaterial({ color: 0xbbccee, roughness: 0.95 });
const quilt = new THREE.Mesh(quiltGeo, quiltMat);
// 顶点随机偏移
const posAttr = quiltGeo.attributes.position;
for(let i=0; i<posAttr.count; i++) {
posAttr.setX(i, posAttr.getX(i) + (Math.random() - 0.5) * 0.2);
posAttr.setY(i, posAttr.getY(i) + (Math.random() - 0.5) * 0.2);
posAttr.setZ(i, posAttr.getZ(i) + (Math.random() - 0.5) * 0.2);
}
quilt.geometry.computeVertexNormals();
quilt.position.set(windowX - 1.5, 3.8, windowZ - 2);
quilt.rotation.z = 0.1; quilt.castShadow = true;
scene.add(quilt);
// 另一床叠好的被子
const quilt2 = new THREE.Mesh(new THREE.BoxGeometry(2, 0.8, 4), matFabricGray);
quilt2.position.set(windowX - 2, 3.5, windowZ - 1.5); quilt2.rotation.y = 0.1; quilt2.castShadow = true; scene.add(quilt2);
// 枕头
const pillow = new THREE.Mesh(new THREE.BoxGeometry(2, 1, 2), new THREE.MeshStandardMaterial({ color: 0xffee88, roughness: 0.9 }));
pillow.position.set(windowX - 1.5, 4, windowZ + 5); pillow.rotation.y = 0.3; pillow.castShadow = true; scene.add(pillow);
// 窗帘 (顶点波浪)
const valanceMat = new THREE.MeshStandardMaterial({ color: 0xfdfaf7, roughness: 0.9 });
const valance = new THREE.Mesh(new THREE.BoxGeometry(0.6, 2.5, 14), valanceMat);
valance.position.set(windowX - 0.8, 12, windowZ); scene.add(valance);
const curtainGeo = new THREE.PlaneGeometry(4, 14, 30, 30);
const curtain = new THREE.Mesh(curtainGeo, valanceMat);
const cPosAttr = curtain.geometry.attributes.position;
for (let i = 0; i < cPosAttr.count; i++) {
const y = cPosAttr.getY(i);
const x = cPosAttr.getX(i);
// 双重波浪
cPosAttr.setZ(i, Math.sin(y * 1.5) * 0.3 + Math.cos(x * 2) * 0.1);
}
curtain.geometry.computeVertexNormals();
curtain.position.set(windowX - 1, 7, windowZ - 6);
curtain.rotation.y = -Math.PI / 2;
scene.add(curtain);
// ==========================================
// 8. 书桌与电脑 (细化)
// ==========================================
const deskGroup = new THREE.Group();
// 桌面
deskGroup.add(new THREE.Mesh(new THREE.BoxGeometry(7, 0.3, 3), matWood)).castShadow = true;
// 抽屉
const drawer = new THREE.Mesh(new THREE.BoxGeometry(2.5, 4, 2.8), matDarkWood);
drawer.position.set(-2.2, -2.15, 0); drawer.castShadow = true; deskGroup.add(drawer);
// 抽屉把手
const drawerHandle = new THREE.Mesh(new THREE.BoxGeometry(1, 0.1, 0.1), matMetal);
drawerHandle.position.set(-2.2, -1, 1.45); deskGroup.add(drawerHandle);
// 显示器
const monitorGroup = new THREE.Group();
monitorGroup.add(new THREE.Mesh(new THREE.CylinderGeometry(0.6, 0.8, 0.2, 16), matMetal)).position.y = 0.1;
monitorGroup.add(new THREE.Mesh(new THREE.CylinderGeometry(0.1, 0.1, 1.2, 8), matMetal)).position.y = 0.8;
const screen = new THREE.Mesh(new THREE.BoxGeometry(2.5, 1.5, 0.1), new THREE.MeshStandardMaterial({ color: 0x111111, roughness: 0.2 }));
screen.position.y = 1.6; monitorGroup.add(screen);
// 屏幕反光面
const screenFace = new THREE.Mesh(new THREE.PlaneGeometry(2.4, 1.4), new THREE.MeshStandardMaterial({ color: 0x222222, roughness: 0.05, metalness: 0.2 }));
screenFace.position.set(0, 1.6, 0.06); monitorGroup.add(screenFace);
monitorGroup.position.set(1.5, 0.2, -0.5);
deskGroup.add(monitorGroup);
// 键盘与鼠标
const keyboard = new THREE.Mesh(new THREE.BoxGeometry(1.5, 0.1, 0.5), new THREE.MeshStandardMaterial({ color: 0x333333, roughness: 0.5 }));
keyboard.position.set(1.5, 0.2, 0.8); deskGroup.add(keyboard);
const mouse = new THREE.Mesh(new THREE.SphereGeometry(0.2, 16, 16), new THREE.MeshStandardMaterial({ color: 0x333333, roughness: 0.5 }));
mouse.scale.set(0.8, 0.5, 1.2); mouse.position.set(2.5, 0.2, 0.8); deskGroup.add(mouse);
// 电脑主机 (带散热孔)
const pcTower = new THREE.Mesh(new THREE.BoxGeometry(1.2, 2.8, 2.2), new THREE.MeshStandardMaterial({ color: 0x1a1a1a, roughness: 0.3, metalness: 0.5 }));
pcTower.position.set(3, -1.4, 0.5); deskGroup.add(pcTower);
// 主机上的蓝色指示灯
const led = new THREE.Mesh(new THREE.BoxGeometry(0.05, 0.05, 0.05), new THREE.MeshBasicMaterial({ color: 0x00aaff }));
led.position.set(3, -0.5, 1.65); deskGroup.add(led);
// 电线 (使用曲线)
const wireCurve = new THREE.CatmullRomCurve3([
new THREE.Vector3(3, -0.5, 1.6),
new THREE.Vector3(2, -1, 2),
new THREE.Vector3(0, -1.5, 2),
new THREE.Vector3(-1, -2, 1)
]);
const wire = new THREE.Mesh(new THREE.TubeGeometry(wireCurve, 20, 0.05, 8, false), new THREE.MeshStandardMaterial({ color: 0x222222, roughness: 0.8 }));
deskGroup.add(wire);
deskGroup.position.set(9, 3, -14);
scene.add(deskGroup);
// ==========================================
// 9. 粉色转椅 (带曲面)
// ==========================================
const chairGroup = new THREE.Group();
// 靠背 (使用曲面)
const backGeo = new THREE.CylinderGeometry(1.5, 1.5, 2.5, 32, 1, true, 0, Math.PI);
const chairBack = new THREE.Mesh(backGeo, matFabricPink);
chairBack.scale.set(1, 1, 0.6);
chairBack.position.set(0, 1.5, -0.8);
chairBack.castShadow = true; chairGroup.add(chairBack);
// 坐垫
const cushion = new THREE.Mesh(new THREE.BoxGeometry(2, 0.4, 2), matFabricPink);
cushion.castShadow = true; chairGroup.add(cushion);
// 气压杆
const pole = new THREE.Mesh(new THREE.CylinderGeometry(0.15, 0.15, 1.5, 12), matMetal);
pole.position.set(0, -0.8, 0); chairGroup.add(pole);
// 五星脚
const base = new THREE.Mesh(new THREE.CylinderGeometry(1.5, 1.5, 0.1, 32), matMetal);
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 wheel = new THREE.Mesh(new THREE.CylinderGeometry(0.15, 0.15, 0.1, 16), matDarkWood);
wheel.rotation.z = Math.PI / 2;
wheel.position.set(Math.cos(angle) * 1.4, -1.6, Math.sin(angle) * 1.4);
wheel.castShadow = true;
chairGroup.add(wheel);
}
chairGroup.position.set(7, 2.5, -11);
scene.add(chairGroup);
// ==========================================
// 10. 空调与风扇
// ==========================================
// 空调
const acGroup = new THREE.Group();
acGroup.add(new THREE.Mesh(new THREE.BoxGeometry(4.5, 1.5, 1), new THREE.MeshStandardMaterial({ color: 0xffffff, roughness: 0.3 }))).castShadow = true;
// 出风口
const acVent = new THREE.Mesh(new THREE.BoxGeometry(4.1, 0.2, 0.1), new THREE.MeshStandardMaterial({ color: 0xdddddd, roughness: 0.5 }));
acVent.position.set(0, -0.55, 0.45); acGroup.add(acVent);
// 显示屏
const acDisplay = new THREE.Mesh(new THREE.BoxGeometry(0.5, 0.2, 0.05), new THREE.MeshBasicMaterial({ color: 0x00ff00 }));
acDisplay.position.set(1.5, 0.2, 0.51); acGroup.add(acDisplay);
acGroup.position.set(0, 13, -17.6);
scene.add(acGroup);
// 落地风扇 (精细网罩)
const fanGroup = new THREE.Group();
// 底座
fanGroup.add(new THREE.Mesh(new THREE.CylinderGeometry(1.5, 1.5, 0.2, 32), new THREE.MeshStandardMaterial({ color: 0xeeeeee, roughness: 0.4 }))).castShadow = true;
// 杆
const fanPole = new THREE.Mesh(new THREE.CylinderGeometry(0.1, 0.1, 5, 16), new THREE.MeshStandardMaterial({ color: 0xeeeeee, roughness: 0.4 }));
fanPole.position.y = 2.5; fanPole.castShadow = true; fanGroup.add(fanPole);
// 控制面板
const panel = new THREE.Mesh(new THREE.BoxGeometry(0.3, 0.8, 0.1), new THREE.MeshStandardMaterial({ color: 0xdddddd, roughness: 0.3 }));
panel.position.set(0.2, 1.5, 0); fanGroup.add(panel);
// 电机外壳
const motor = new THREE.Mesh(new THREE.CylinderGeometry(0.6, 0.6, 0.8, 16), new THREE.MeshStandardMaterial({ color: 0xcccccc, roughness: 0.3, metalness: 0.5 }));
motor.rotation.x = Math.PI / 2; motor.position.set(0, 5.5, 0.3); fanGroup.add(motor);
// 扇叶
const bladeMat = new THREE.MeshStandardMaterial({ color: 0xeeeeee, roughness: 0.2, side: THREE.DoubleSide });
for(let i=0; i<3; i++) {
const blade = new THREE.Mesh(new THREE.PlaneGeometry(1.2, 0.4), bladeMat);
blade.rotation.z = (i / 3) * Math.PI * 2;
blade.rotation.x = 0.3;
blade.position.set(0, 5.5, 0.1);
blade.rotation.y = (i / 3) * Math.PI * 2;
fanGroup.add(blade);
}
// 网罩 (用多个圆环和径向线组合)
const ringMat = new THREE.MeshStandardMaterial({ color: 0xaaaaaa, roughness: 0.4, metalness: 0.6 });
for(let r=0.5; r<=1.6; r+=0.3) {
const ring = new THREE.Mesh(new THREE.TorusGeometry(r, 0.02, 8, 32), ringMat);
ring.position.set(0, 5.5, 0); ring.rotation.y = Math.PI / 2;
fanGroup.add(ring);
}
for(let i=0; i<16; i++) {
const angle = (i / 16) * Math.PI * 2;
const spoke = new THREE.Mesh(new THREE.CylinderGeometry(0.01, 0.01, 3.2, 6), ringMat);
spoke.rotation.z = Math.PI / 2;
spoke.rotation.y = angle;
spoke.position.set(0, 5.5, 0);
fanGroup.add(spoke);
}
fanGroup.position.set(-8, 0, -8);
fanGroup.rotation.y = 0.3;
scene.add(fanGroup);
// ==========================================
// 11. 床边细节 (床头柜、台灯、玩偶)
// ==========================================
const bedsideX = 14, bedsideZ = 12;
// 床头板
const headboard = new THREE.Mesh(new THREE.BoxGeometry(22, 3.5, 0.4), matWall);
headboard.position.set(-2, 2.5, 18.2); headboard.receiveShadow = true; scene.add(headboard);
// 床头柜
const nightstand = new THREE.Mesh(new THREE.BoxGeometry(3, 4, 3), new THREE.MeshStandardMaterial({ color: 0xffffff, roughness: 0.6 }));
nightstand.position.set(bedsideX, 2, bedsideZ); nightstand.castShadow = true; nightstand.receiveShadow = true; scene.add(nightstand);
// 抽屉 (半开)
const nsDrawer = new THREE.Mesh(new THREE.BoxGeometry(2.8, 1.5, 0.2), new THREE.MeshStandardMaterial({ color: 0xe0e0e0, roughness: 0.6 }));
nsDrawer.position.set(bedsideX, 1.5, bedsideZ + 1.6); scene.add(nsDrawer);
// 抽屉把手
const nsHandle = new THREE.Mesh(new THREE.BoxGeometry(1.2, 0.1, 0.1), matMetal);
nsHandle.position.set(bedsideX, 1.5, bedsideZ + 1.75); scene.add(nsHandle);
// 抽屉内部杂物
const tissueBox = new THREE.Mesh(new THREE.BoxGeometry(1, 0.6, 0.5), new THREE.MeshStandardMaterial({ color: 0xd8bfd8, roughness: 0.9 }));
tissueBox.position.set(bedsideX - 0.6, 1.8, bedsideZ + 1.2); scene.add(tissueBox);
const tissuePack = new THREE.Mesh(new THREE.BoxGeometry(0.6, 0.4, 0.4), new THREE.MeshStandardMaterial({ color: 0xadd8e6, roughness: 0.9 }));
tissuePack.position.set(bedsideX + 0.6, 1.7, bedsideZ + 1.3); scene.add(tissuePack);
// 红色折叠台灯
const lampGroup = new THREE.Group();
const lampBase = new THREE.Mesh(new THREE.CylinderGeometry(1, 1, 0.3, 16), new THREE.MeshStandardMaterial({ color: 0xff3333, roughness: 0.4 }));
lampBase.position.y = 0.15; lampGroup.add(lampBase);
// 关节臂
const arm1 = new THREE.Mesh(new THREE.CylinderGeometry(0.1, 0.1, 2.5, 8), matDarkWood);
arm1.position.set(0, 1.5, 0); arm1.rotation.z = 0.4; lampGroup.add(arm1);
const arm2 = new THREE.Mesh(new THREE.CylinderGeometry(0.08, 0.08, 2.5, 8), matDarkWood);
arm2.position.set(0.8, 3.5, 0); arm2.rotation.z = -0.6; lampGroup.add(arm2);
// 灯罩
const lampshade = new THREE.Mesh(new THREE.ConeGeometry(1.2, 1.8, 16, 1, true), new THREE.MeshStandardMaterial({ color: 0xff4444, roughness: 0.3, side: THREE.DoubleSide }));
lampshade.position.set(1.5, 5, 0); lampshade.rotation.z = -0.8; lampGroup.add(lampshade);
// 灯泡发光效果
const bulb = new THREE.Mesh(new THREE.SphereGeometry(0.4, 16, 16), new THREE.MeshBasicMaterial({ color: 0xffdd88 }));
bulb.position.set(1.2, 4.2, 0); lampGroup.add(bulb);
lampGroup.position.set(bedsideX - 0.5, 4, bedsideZ - 0.5);
scene.add(lampGroup);
// 床上毛绒小熊 (灰粉色)
const bearGroup = new THREE.Group();
const bearMat = new THREE.MeshStandardMaterial({ color: 0x999999, roughness: 0.9 });
const bearHead = new THREE.Mesh(new THREE.SphereGeometry(1, 16, 16), bearMat);
bearHead.position.y = 1.2; bearGroup.add(bearHead);
const bearBody = new THREE.Mesh(new THREE.CylinderGeometry(0.8, 1, 2, 16), bearMat);
bearBody.position.y = 0; bearGroup.add(bearBody);
// 耳朵
const earL = new THREE.Mesh(new THREE.SphereGeometry(0.3, 8, 8), bearMat);
earL.position.set(-0.8, 2, 0); bearGroup.add(earL);
const earR = earL.clone(); earR.position.x = 0.8; bearGroup.add(earR);
// 粉色眼罩
const eyeMask = new THREE.Mesh(new THREE.BoxGeometry(1.5, 0.4, 0.3), matFabricPink);
eyeMask.position.set(0, 1.4, 0.9); bearGroup.add(eyeMask);
// 鼻子
const nose = new THREE.Mesh(new THREE.SphereGeometry(0.15, 8, 8), new THREE.MeshStandardMaterial({ color: 0x333333 }));
nose.position.set(0, 1, 1); bearGroup.add(nose);
bearGroup.position.set(-5, 1.5, 16);
bearGroup.rotation.y = 0.6;
bearGroup.scale.set(0.8, 0.8, 0.8);
scene.add(bearGroup);
// 蝴蝶墙贴 (使用 Canvas 动态绘制)
function createButterflyTexture() {
const canvas = document.createElement('canvas'); canvas.width = 1024; canvas.height = 256;
const ctx = canvas.getContext('2d');
ctx.clearRect(0, 0, 1024, 256);
const colors = ['#ff6b6b', '#4ecdc4', '#ffe66d', '#a06cd5', '#ff9f1c'];
for(let i=0; i<8; i++) {
ctx.fillStyle = colors[i % colors.length];
const x = 80 + i * 120;
const y = 128 + Math.sin(i) * 40;
// 画蝴蝶翅膀
ctx.beginPath(); ctx.ellipse(x, y, 20, 40, Math.PI/6, 0, Math.PI*2); ctx.fill();
ctx.beginPath(); ctx.ellipse(x, y, 20, 40, -Math.PI/6, 0, Math.PI*2); ctx.fill();
// 身体
ctx.fillStyle = '#333'; ctx.beginPath(); ctx.ellipse(x, y, 4, 25, 0, 0, Math.PI*2); ctx.fill();
}
return new THREE.CanvasTexture(canvas);
}
const butterflyMat = new THREE.MeshStandardMaterial({ map: createButterflyTexture(), transparent: true, depthWrite: false });
const butterflyPlane = new THREE.Mesh(new THREE.PlaneGeometry(20, 5), butterflyMat);
butterflyPlane.position.set(-2, 6, 18);
butterflyPlane.rotation.y = Math.PI;
scene.add(butterflyPlane);
// ==========================================
// 12. 前景床铺 (凉席顶点变形)
// ==========================================
// 凉席
const matGeo = new THREE.BoxGeometry(24, 0.6, 14, 20, 2, 20);
const matTexture = createMatTexture();
const bedMat = new THREE.MeshStandardMaterial({ map: matTexture, roughness: 0.95 });
const bedBase = new THREE.Mesh(matGeo, bedMat);
// 让床铺边缘有轻微的起伏
const bPosAttr = matGeo.attributes.position;
for(let i=0; i<bPosAttr.count; i++) {
const x = bPosAttr.getX(i);
const z = bPosAttr.getZ(i);
// 边缘翘起
if (Math.abs(x) > 10 || Math.abs(z) > 6) {
bPosAttr.setY(i, bPosAttr.getY(i) + Math.random() * 0.1);
}
}
bedBase.geometry.computeVertexNormals();
bedBase.position.set(-2, 0.3, 15);
bedBase.receiveShadow = true;
scene.add(bedBase);
// 床铺白边
const bedEdgeMat = new THREE.MeshStandardMaterial({ color: 0xd3d3d3, roughness: 0.8 });
const bedFrontEdge = new THREE.Mesh(new THREE.BoxGeometry(24.2, 0.2, 0.6), bedEdgeMat);
bedFrontEdge.position.set(-2, 0.8, 21.8); bedFrontEdge.receiveShadow = true; scene.add(bedFrontEdge);
// 灰色靠背大抱枕
const bigPillowGeo = new THREE.BoxGeometry(6, 4, 1.5, 10, 10, 10);
const bigPillow = new THREE.Mesh(bigPillowGeo, matFabricGray);
// 抱枕褶皱
const pPosAttr = bigPillowGeo.attributes.position;
for(let i=0; i<pPosAttr.count; i++) {
const x = pPosAttr.getX(i);
const y = pPosAttr.getY(i);
pPosAttr.setZ(i, pPosAttr.getZ(i) + Math.sin(x*2) * 0.1 + Math.cos(y*2) * 0.1);
}
bigPillow.geometry.computeVertexNormals();
bigPillow.position.set(-6, 3, 17); bigPillow.rotation.x = 0.1; bigPillow.rotation.y = 0.2;
bigPillow.castShadow = true; scene.add(bigPillow);
// 紫色花纹抱枕
const patternPillow = new THREE.Mesh(new THREE.BoxGeometry(4, 1.5, 2.5), new THREE.MeshStandardMaterial({ color: 0xd8bfd8, roughness: 0.9 }));
patternPillow.position.set(-2, 1.5, 16); patternPillow.rotation.y = -0.3;
patternPillow.castShadow = true; scene.add(patternPillow);
// ==========================================
// 13. 动画与渲染
// ==========================================
function animate() {
requestAnimationFrame(animate);
// 风扇旋转动画
if (fanGroup.children[4]) { // 扇叶组
fanGroup.children[4].rotation.z += 0.2;
}
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 (691 lines, 35.5 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-mujjvr59" to link back to the original. Then publish at arcadelab.ai/publish.