3、渲染器(Renderer):WebGL渲染器、渲染循环、设置背景色、自适应窗口大小

渲染器,说白了就是 Three.js 的「画板」。场景和相机都准备好了,总得有个东西把它们画到屏幕上吧?这个活儿,就交给渲染器。

我个人习惯把渲染器理解成「翻译官」——它把 Three.js 里的 3D 数据,翻译成浏览器能懂的像素点。你想想看,没有它,我们看到的就只是一堆数学坐标而已。

3.1 WebGL渲染器:核心中的核心

Three.js 最常用的渲染器是 WebGLRenderer。它基于 WebGL 技术,利用 GPU 来加速渲染。我刚开始学的时候,以为渲染器有很多种选择,后来发现,99% 的场景下,用 WebGLRenderer 就够了。

创建它非常简单:

const renderer = new THREE.WebGLRenderer();
document.body.appendChild(renderer.domElement);

这里有个细节:renderer.domElement 是一个 canvas 元素。Three.js 把所有的 3D 内容都画在这个 canvas 上。然后我们把它挂到页面上。

小提示: 如果你不想让 canvas 直接挂在 body 下,也可以挂到某个 div 里。比如:document.getElementById('container').appendChild(renderer.domElement)。这样布局更灵活。

3.2 设置渲染器尺寸

创建完渲染器,第一件事就是设置它的尺寸。不设置的话,默认是 300x150 像素,太小了。

renderer.setSize(window.innerWidth, window.innerHeight);

嗯,这里要注意:setSize 设置的是 canvas 的像素尺寸。如果你想让 canvas 铺满整个窗口,就用窗口的宽高。

3.3 设置背景色

场景的背景色,很多人以为是在 Scene 里设置的。其实,渲染器也能设置背景色。我个人习惯在渲染器里设置,因为更直接。

renderer.setClearColor(0x87CEEB); // 天蓝色

或者你想用带透明度的颜色:

renderer.setClearColor(0x87CEEB, 0.5); // 半透明天蓝色
核心区别: scene.backgroundrenderer.setClearColor 都能设置背景色。但前者是场景的属性,后者是渲染器的属性。如果你设置了 scene.background,它会覆盖渲染器的背景色。我一般只用渲染器的 setClearColor,逻辑更清晰。

3.4 渲染循环:让画面动起来

静态的 3D 场景没什么意思。要让物体动起来,就需要一个循环,不断地重新渲染画面。

最原始的方式是用 setInterval

setInterval(() => {
  renderer.render(scene, camera);
}, 1000 / 60); // 60fps

但这种方式有个问题:它不管浏览器是否在忙,都会强行执行。如果页面被切到后台,它还在跑,浪费性能。

更好的方案是 requestAnimationFrame。它会告诉浏览器:「嘿,下次你刷新屏幕的时候,记得叫我。」

function animate() {
  requestAnimationFrame(animate);
  renderer.render(scene, camera);
}
animate();

为什么推荐这个?因为它会跟浏览器的刷新频率同步。大多数显示器是 60Hz,所以它每秒调用 60 次。如果页面切到后台,它会自动暂停,省电又省资源。

避坑指南: 我曾经在项目里忘了调用 requestAnimationFrame,结果画面一动不动。排查了半天才发现,原来 animate 函数只执行了一次。记住:requestAnimationFrame 只执行一次回调,所以必须在回调里再次调用它,形成循环。

3.5 自适应窗口大小

用户可能会调整浏览器窗口大小。如果我们的 3D 场景不跟着变,就会出现黑边或者画面拉伸。嗯,这里要处理两个东西:相机和渲染器。

相机需要更新宽高比:

window.addEventListener('resize', () => {
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix(); // 告诉相机:你的参数变了
  renderer.setSize(window.innerWidth, window.innerHeight);
});

你想想看,如果不调用 updateProjectionMatrix,相机还是用旧的宽高比去计算画面,结果就是物体变形。我刚开始就踩过这个坑,画面里的立方体变成了长方形,还以为是代码写错了。

小技巧: 如果你用的是 OrthographicCamera(正交相机),自适应时除了更新宽高比,可能还需要调整 left/right/top/bottom 参数。具体看你的场景需求。

3.6 完整的渲染器初始化代码

把上面这些整合起来,就是一个标准的渲染器初始化流程:

// 1. 创建渲染器
const renderer = new THREE.WebGLRenderer({ antialias: true }); // 开启抗锯齿
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setClearColor(0x222222); // 深灰色背景

// 2. 挂载到页面
document.body.appendChild(renderer.domElement);

// 3. 渲染循环
function animate() {
  requestAnimationFrame(animate);
  renderer.render(scene, camera);
}
animate();

// 4. 自适应窗口
window.addEventListener('resize', () => {
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();
  renderer.setSize(window.innerWidth, window.innerHeight);
});
核心总结: 渲染器是 Three.js 的「输出设备」。记住三个关键点:
1. 用 WebGLRenderer 创建
2. 用 requestAnimationFrame 驱动循环
3. 用 resize 事件处理自适应

我个人习惯把渲染器的初始化单独封装成一个函数,这样在多个项目里复用起来很方便。你试试看,把上面这段代码跑起来,就能看到一个深灰色的 3D 画布了。虽然现在还没有物体,但舞台已经搭好了。