Skip to content

81. 实战:3D 电脑(一)

Published:

前面学了 CSS3DRenderer 一直没练习下:

2025-04-08 00.09.03.gif

这节我们就来做一个实战:

image.png

我们来实现一个这样的电脑,并且是可以操作的,屏幕可以点击、滚动那种。

这种需求很明显要用 CSS3DRenderer 结合网页来实现。

这里网页渲染我们用 React 前端框架。

创建项目:

npx create-vite css3d-computer

image.png

选择 react、js 来创建 vite 项目。

进入项目,安装依赖:

pnpm install
pnpm install --save three
pnpm install --save-dev @types/three

去掉 StrictMode 和 index.css

image.png

然后改一下 App.jsx

import { useEffect, useRef, useState } from 'react';
import { init } from './3d-init'
import './App.css'

function App() {

  useEffect(() => {
    const dom = document.getElementById('content');
    const { scene } = init(dom);
  
    return () => {
      dom.innerHTML = '';
    }
  }, []);

  return <div>
    <div id="main">
      <div id="content">
      </div>
    </div>
  </div>
}

export default App

在 App.css 写下样式:

body {
  margin: 0;
}

然后来初始化 3d 场景:

创建 3d-init.js

import * as THREE from 'three';
import {
    OrbitControls
} from 'three/addons/controls/OrbitControls.js';
import mesh from './mesh';

export function init(dom) {

    const scene = new THREE.Scene();
    scene.add(mesh);

    const axesHelper = new THREE.AxesHelper(500);
    scene.add(axesHelper);

    const directionalLight = new THREE.DirectionalLight(0xffffff);
    directionalLight.position.set(500, 400, 300);
    scene.add(directionalLight);

    const ambientLight = new THREE.AmbientLight(0xffffff);
    scene.add(ambientLight);

    const width = window.innerWidth;
    const height = window.innerHeight;

    const camera = new THREE.PerspectiveCamera(60, width / height, 1, 10000);
    camera.position.set(0, 500, 500);
    camera.lookAt(0, 0, 0);

    const renderer = new THREE.WebGLRenderer({
        antialias: true
    });
    renderer.setSize(width, height);

    const controls = new OrbitControls(camera, renderer.domElement);

    function render(time) {
        controls.update(time);
        renderer.render(scene, camera);
        requestAnimationFrame(render);
    }

    render();

    dom.append(renderer.domElement);

    window.onresize = function () {
        const width = window.innerWidth;
        const height = window.innerHeight;

        renderer.setSize(width,height);

        camera.aspect = width / height;
        camera.updateProjectionMatrix();
    };

    return {
        scene,
        renderer,
        controls
    }
}

创建 mesh.js

import * as THREE from 'three';

const group = new THREE.Group();

const geometry = new THREE.BoxGeometry(100, 100, 100);
const material = new THREE.MeshLambertMaterial({
    color: 'orange'
});
const mesh = new THREE.Mesh(geometry, material);

group.add(mesh);

export default group;

我们先跑起来看下:

npm run dev

image.png

2025-07-03 10.26.37.gif

然后从 sketchfab 找个电脑模型:

https://sketchfab.com/3d-models/desktop-monitor-low-poly-a30bb55b7e774b1fa696edbee9588835#download

image.png

下载下来放到 public 目录:

image.png

image.png

改下 mesh.js 加载下模型:

import * as THREE from 'three';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';

const loader = new GLTFLoader();

const mesh = new THREE.Group();

loader.load("./monitor.glb", function (gltf) {
    console.log(gltf);
    mesh.add(gltf.scene);
    gltf.scene.scale.set(300, 300, 300);
})

export default mesh;

2025-07-03 10.32.12.gif

调整下相机的位置和 lookAt:

image.png

相机位置是从 x 轴正方向看过去,焦点在电脑的中心,差不多 100 高度的位置。

注意,lookAt 改了要修改 controls 的 target,不然会重置到 0,0,0

image.png

camera.position.set(600, 500, 0);
camera.lookAt(0, 100, 0);
controls.target.set(0, 100, 0);

2025-07-03 10.37.11.gif

这样就好了。

改一下背景色:

image.png

renderer.setClearColor('lightyellow');

2025-07-03 10.38.47.gif

电脑后面没有反光,有点假。

我们调一下平行光位置和强度:

image.png

const directionalLight = new THREE.DirectionalLight(0xffffff, 10);
directionalLight.position.set(-100, 1000, 0);
directionalLight.lookAt(0, 0, 0);

灯光在电脑上方偏后的位置,强度调为 10

2025-07-03 10.44.42.gif

这样就好多了。

然后我们加个办公桌:

https://sketchfab.com/3d-models/computer-desk-05353724b7884bfb81211c7033a57fd4

image.png

下载下来放 public 目录下:

image.png

加载进来:

image.png

loader.load("./desk.glb", function (gltf) {
    console.log(gltf);
    mesh.add(gltf.scene);
    gltf.scene.scale.set(200, 200, 200);
})

2025-07-03 10.50.54.gif

调一下旋转角度:

gltf.scene.rotateY(Math.PI / 2);

image.png

有点近了,调远点:

image.png

2025-07-03 10.54.39.gif

背景色还是改为浅蓝色:

renderer.setClearColor('lightblue');

去掉坐标轴:

image.png

image.png

然后我们加一下阴影:

给电脑开启投射阴影:

image.png

gltf.scene.traverse(obj => {
    obj.castShadow = true;
});

renderer 开启阴影渲染:

image.png

renderer.shadowMap.enabled = true;

然后给灯光开启投射阴影,设置下阴影矩阵:

image.png

directionalLight.castShadow = true;
directionalLight.shadow.camera.left = -200;
directionalLight.shadow.camera.right = 200;
directionalLight.shadow.camera.top = 100;
directionalLight.shadow.camera.bottom = -100;
directionalLight.shadow.camera.near = 0.5;
directionalLight.shadow.camera.far = 1000;

const cameraHelper = new THREE.CameraHelper(directionalLight.shadow.camera);
scene.add(cameraHelper);

image.png

投影矩阵有点小,调大一点:

directionalLight.castShadow = true;
directionalLight.shadow.camera.left = -800;
directionalLight.shadow.camera.right = 800;
directionalLight.shadow.camera.top = 500;
directionalLight.shadow.camera.bottom = -500;
directionalLight.shadow.camera.near = 0.5;
directionalLight.shadow.camera.far = 2000;

2025-07-03 11.14.40.gif

现在投影矩阵大小就正好了。

那为啥没有产生阴影呢?

因为没有物体接收阴影啊。

给桌子开启接收阴影。

image.png

gltf.scene.traverse(obj => {
    obj.receiveShadow = true;
});

image.png

这样就有阴影了。

去掉 cameraHelper 看一下:

2025-07-03 11.19.08.gif

场景搭建完成。

案例代码上传了小册仓库

总结

这节我们搭建了电脑、桌面的场景。

从 sketchfab.com 找的电脑和桌子的模型,调节了相机的角度和 lookAt 还有灯光位置,加上了阴影。

下节我们来用 CSS3DRenderer 加上电脑屏幕的内容。

评论