HTML5版3D实验室系列【二】----摄像机、投影、3D旋转以及缩放(演示很多,请自备瓜子眼药水··)

上一集我们搭好了3D场景的“毛坯房”——画了个能转的立方体线框。今天咱们来给这毛坯房装上“摄像机”、“投影仪”,再教它“扭秧歌”和“变焦”。放心,代码不多,但视觉效果绝对让你瞪大眼睛,建议备好瓜子可乐,我们边吃边看。### 1. 摄像机:原来我们一直站在原点偷看你有没有想过,我们之前画的立方体,为什么看起来是“正对着”我们的?因为默认的摄像机就放在(0,0,0)点,镜头朝Z轴负方向。就像你站在舞台正中央,盯着正前方看。但真实世界里,我们得能“移动摄像机”——比如绕到物体侧面看,或者从高处俯拍。在WebGL/Three.js里,移动摄像机就相当于移动你的眼睛。核心概念:摄像机位置(camera.position)、观察目标(camera.lookAt)、上方向(camera.up)。这三者决定了你“看到什么”。咱们来个小Demo:让摄像机绕着一个立方体转圈,就像你围着展品走动欣赏。javascript// 场景、相机、渲染器(老规矩,先搭台子)const scene = new THREE.Scene();const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);const renderer = new THREE.WebGLRenderer();renderer.setSize(window.innerWidth, window.innerHeight);document.body.appendChild(renderer.domElement);// 放一个正方体,带点颜色,不然太素const geometry = new THREE.BoxGeometry(1, 1, 1);const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });const cube = new THREE.Mesh(geometry, material);scene.add(cube);// 关键:摄像机初始位置在(2,2,5),看向原点camera.position.set(2, 2, 5);camera.lookAt(0, 0, 0);// 动画:让摄像机绕Y轴旋转,同时保持看向原点function animate() { requestAnimationFrame(animate); // 每帧更新摄像机位置(圆周运动) const time = Date.now() * 0.001; const radius = 5; camera.position.x = radius * Math.sin(time); camera.position.z = radius * Math.cos(time); camera.position.y = 2; // 保持一定高度 camera.lookAt(0, 0, 0); // 始终盯住原点 renderer.render(scene, camera);}animate();运行这段代码,你会看到绿色立方体在原地,而我们“摄像机”像卫星一样绕着它转。这就是摄像机移动的魅力——物体没动,但视角变了,世界就变了。### 2. 投影:从3D到2D的“魔法压缩”我们生活在3D世界,但屏幕是2D的。投影就是那道“压缩魔法”。Three.js里最常用的是透视投影(PerspectiveCamera),它模拟人眼——近大远小,有立体感。另一个是正交投影(OrthographicCamera),就像工程制图,不管远近,大小一样。透视投影参数:视场角(fov,越大越“广角”)、宽高比(aspect)、近裁剪面(near)、远裁剪面(far)。这四个参数决定你的“视野范围”。咱们看看两种投影的直观区别:javascript// 创建两个渲染器,并排显示const scene = new THREE.Scene();// 添加一个甜甜圈一样的环(用TorusGeometry)const torusGeo = new THREE.TorusGeometry(1, 0.4, 16, 100);const torusMat = new THREE.MeshNormalMaterial();const torus = new THREE.Mesh(torusGeo, torusMat);scene.add(torus);// 透视相机:视场角60度,看起来有纵深const perspectiveCamera = new THREE.PerspectiveCamera(60, 1, 0.1, 100);perspectiveCamera.position.set(3, 2, 5);perspectiveCamera.lookAt(0, 0, 0);// 正交相机:无视距离,前后一样大const orthoCamera = new THREE.OrthographicCamera(-2, 2, 2, -2, 0.1, 100);orthoCamera.position.set(3, 2, 5);orthoCamera.lookAt(0, 0, 0);// 两个渲染器,各占半边屏幕const renderer1 = new THREE.WebGLRenderer();renderer1.setSize(window.innerWidth / 2, window.innerHeight);document.body.appendChild(renderer1.domElement);const renderer2 = new THREE.WebGLRenderer();renderer2.setSize(window.innerWidth / 2, window.innerHeight);renderer2.domElement.style.position = 'absolute';renderer2.domElement.style.left = window.innerWidth / 2 + 'px';document.body.appendChild(renderer2.domElement);// 旋转环,让它转起来function animate() { requestAnimationFrame(animate); torus.rotation.x += 0.01; torus.rotation.y += 0.02; renderer1.render(scene, perspectiveCamera); renderer2.render(scene, orthoCamera);}animate();左边是透视效果,右边是正交效果。你会看到左边环的远近部分大小不同,而右边完全一样平。这就是“投影”的两种流派。### 3. 3D旋转:不只是绕轴转,还能“万向锁”搞事情旋转在3D里是个大坑。最简单的做法是绕X、Y、Z三个轴分别转(欧拉角)。但玩过《星际公民》的朋友都知道,欧拉角会遇到“万向锁”——两个轴重合后,丢失一个自由度,飞船会像喝醉了一样乱转。所以在Three.js里,我们更推荐用四元数(Quaternion)来做旋转。它没有万向锁问题,而且插值平滑。来看一个用四元数让物体自由旋转的Demo:javascriptconst scene = new THREE.Scene();const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);const renderer = new THREE.WebGLRenderer();renderer.setSize(window.innerWidth, window.innerHeight);document.body.appendChild(renderer.domElement);// 一个漂亮的二十面体(像水晶)const geo = new THREE.IcosahedronGeometry(1, 0);const mat = new THREE.MeshPhongMaterial({ color: 0xffaa00, shininess: 100 });const mesh = new THREE.Mesh(geo, mat);scene.add(mesh);// 加一点光源,不然看不出立体感const light = new THREE.DirectionalLight(0xffffff, 1);light.position.set(5, 5, 5);scene.add(light);camera.position.set(0, 0, 4);// 用四元数旋转:每次绕一个随机轴转一个小角度const quaternion = new THREE.Quaternion();const axis = new THREE.Vector3(1, 1, 0).normalize(); // 归一化轴向量function animate() { requestAnimationFrame(animate); // 每次绕axis轴旋转0.02弧度(大约1.15度) const deltaQuat = new THREE.Quaternion().setFromAxisAngle(axis, 0.02); quaternion.multiply(deltaQuat); // 累积旋转 mesh.quaternion.copy(quaternion); renderer.render(scene, camera);}animate();这个二十面体会绕着一个倾斜的轴匀速旋转,姿态平滑,不会出现“抽搐”。四元数虽然概念抽象,但用起来就是一个乘法而已。### 4. 缩放:不是拉近相机,而是改变“焦距”很多人以为缩放是移动摄像机位置,其实不然。真正的缩放(Zoom)是改变摄像机的视场角(fov)——fov越小,相当于长焦镜头,物体被“拉近”但透视变弱;fov越大,相当于广角,视野变大但物体变小。来做个交互式Demo:鼠标滚轮控制缩放。javascriptconst scene = new THREE.Scene();const camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 100);const renderer = new THREE.WebGLRenderer();renderer.setSize(window.innerWidth, window.innerHeight);document.body.appendChild(renderer.domElement);// 一个球体,带纹理感const sphereGeo = new THREE.SphereGeometry(1, 32, 32);const sphereMat = new THREE.MeshNormalMaterial();const sphere = new THREE.Mesh(sphereGeo, sphereMat);scene.add(sphere);camera.position.set(3, 1, 3);camera.lookAt(0, 0, 0);// 监听鼠标滚轮事件window.addEventListener('wheel', (event) => { // 滚轮向上:fov减小(拉近);向下:fov增大(拉远) camera.fov += event.deltaY * 0.05; // 限制fov范围,防止过度缩放 camera.fov = Math.max(20, Math.min(100, camera.fov)); camera.updateProjectionMatrix(); // 关键!更新投影矩阵});function animate() { requestAnimationFrame(animate); sphere.rotation.y += 0.01; renderer.render(scene, camera);}animate();滚动滚轮试试,球体会“呼吸”般地放大缩小。注意updateProjectionMatrix()这个函数,改了fov必须调用它,不然相机不会重新计算投影。### 5. 综合实战:一个可交互的3D展示台学了这么多,咱们来个全家桶:摄像机可拖拽旋转、滚轮缩放、物体自身旋转。html<!DOCTYPE html><html><head><title>3D展示台</title></head><body style="margin:0; overflow:hidden;"><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>// 场景、相机、渲染器const scene = new THREE.Scene();scene.background = new THREE.Color(0x222222);const camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);camera.position.set(5, 5, 5);const renderer = new THREE.WebGLRenderer({ antialias: true });renderer.setSize(window.innerWidth, window.innerHeight);document.body.appendChild(renderer.domElement);// 添加一个“产品”——用多个几何体组合const group = new THREE.Group();const box = new THREE.Mesh(new THREE.BoxGeometry(1, 1, 1), new THREE.MeshPhongMaterial({ color: 0x44aaff }));const sphere = new THREE.Mesh(new THREE.SphereGeometry(0.5, 16, 16), new THREE.MeshPhongMaterial({ color: 0xff6644 }));sphere.position.set(1.2, 0.3, 0);const cylinder = new THREE.Mesh(new THREE.CylinderGeometry(0.3, 0.3, 1.5), new THREE.MeshPhongMaterial({ color: 0x88dd88 }));cylinder.position.set(-1.2, -0.2, 0.5);group.add(box, sphere, cylinder);scene.add(group);// 光源const ambientLight = new THREE.AmbientLight(0x404040);scene.add(ambientLight);const dirLight = new THREE.DirectionalLight(0xffffff, 1);dirLight.position.set(5, 10, 7);scene.add(dirLight);// 轨道控制器(实现鼠标拖拽旋转、滚轮缩放)const controls = new THREE.OrbitControls(camera, renderer.domElement);controls.enableDamping = true; // 开启惯性controls.dampingFactor = 0.05;// 动画循环function animate() { requestAnimationFrame(animate); // 让组合体自身缓慢旋转 group.rotation.y += 0.005; controls.update(); // 更新控制器状态 renderer.render(scene, camera);}animate();// 窗口大小自适应window.addEventListener('resize', () => { camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth, window.innerHeight);});</script></body></html>这个Demo里,你可以鼠标左键拖拽旋转视角,滚轮缩放,右键拖拽平移。而中间的“产品”自己也在慢慢旋转,非常带感。### 总结今天我们搞定了3D世界的四大基本功:- 摄像机:位置、朝向、上方向决定了你看世界的角度,移动摄像机等于移动你的眼睛。- 投影:透视投影(近大远小)和正交投影(等比例)是两种不同的“压缩”方式,按需选择。- 旋转:欧拉角简单但会万向锁,四元数复杂但无死角,推荐后者。- 缩放:本质是改变视场角fov,别忘了调用updateProjectionMatrix()。掌握了这些,你已经能搭建一个“可逛”的3D场景了。下一集我们聊聊光照与材质,让物体告别塑料感,拥有金属光泽、透明质感。那时候,你的3D实验室才真正有了“灵魂”。瓜子壳别扔,下一集继续磕!

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐