4、渲染器Renderer:WebGLRenderer配置、抗锯齿与阴影、渲染循环(requestAnimationFrame)、性能监控(Stats.js)
渲染器,说白了就是Three.js的「画师」。你把场景、相机、灯光都摆好了,最后得靠它一笔一笔画到屏幕上。我个人习惯把渲染器配置放在项目的最前面,因为很多坑其实在初始化阶段就能避开。
4.1 WebGLRenderer基础配置
先看一个最基础的初始化代码:
const renderer = new THREE.WebGLRenderer({
antialias: true, // 抗锯齿
alpha: false, // 是否透明背景
precision: 'highp', // 精度:highp | mediump | lowp
powerPreference: 'high-performance' // 优先使用独立显卡
});
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
document.body.appendChild(renderer.domElement);
这里有几个关键点,我一个个说。
antialias(抗锯齿):说白了就是让物体边缘不那么「狗啃」。我在项目中遇到过,有些低端手机开了抗锯齿反而卡顿,因为它是通过采样实现的。如果你做移动端项目,建议先关掉,看看效果能不能接受。
setPixelRatio:这个我踩过坑。以前我直接写 window.devicePixelRatio,结果在Retina屏上帧率直接掉到30。后来我习惯限制最大值为2,视觉上差别不大,性能却好很多。
4.2 抗锯齿与阴影的取舍
抗锯齿和阴影,是渲染器里最吃性能的两个功能。你想想看,它们本质上都是在做「额外计算」。
阴影配置:
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap; // 柔和阴影
阴影类型有几种:
| 类型 | 性能 | 效果 | 适用场景 |
|---|---|---|---|
| BasicShadowMap | 最快 | 锯齿明显 | 低端设备、调试 |
| PCFShadowMap | 中等 | 边缘柔和 | 大多数场景 |
| PCFSoftShadowMap | 较慢 | 非常柔和 | 高质量场景 |
| VSMShadowMap | 中等 | 无锯齿但可能漏光 | 特定需求 |
我个人习惯:开发阶段用PCFSoftShadowMap看效果,上线前根据设备降级。嗯,这里要注意——不是所有光源都能投射阴影。只有DirectionalLight、SpotLight、PointLight支持,AmbientLight不行。
light.shadow.mapSize.width = 2048。但别设太高,1024到2048之间比较稳妥。
4.3 渲染循环:requestAnimationFrame
为什么不用setInterval?因为requestAnimationFrame是浏览器专门为动画设计的。它会等浏览器准备好再执行,不会浪费CPU。而且页面切到后台时会自动暂停,省电又省资源。
标准写法:
function animate() {
requestAnimationFrame(animate);
// 更新逻辑
mesh.rotation.x += 0.01;
controls.update();
// 渲染
renderer.render(scene, camera);
}
animate();
这里有个细节:requestAnimationFrame(animate) 要写在函数最前面。为什么?因为如果后面代码报错,至少下一帧还能继续跑。我曾经把这句话写在最后,结果某次更新逻辑抛异常,整个动画就停了,排查了半天。
另外,如果你需要控制帧率,可以加个时间戳判断:
let lastTime = 0;
const targetFPS = 30;
const interval = 1000 / targetFPS;
function animate(time) {
requestAnimationFrame(animate);
const delta = time - lastTime;
if (delta < interval) return;
lastTime = time - (delta % interval);
// 更新和渲染
}
这个技巧我在做AR项目时用过。有些手机跑60帧发热严重,降到30帧用户根本感觉不到,但手机温度降了一大截。
4.4 性能监控:Stats.js
Stats.js是Mr.doob写的一个小工具,就一个功能——显示FPS。但你别小看它,我每次做性能优化都靠它。
用法很简单:
import Stats from 'three/examples/jsm/libs/stats.module.js';
const stats = new Stats();
stats.dom.style.left = '80px'; // 避免和Three.js默认的左上角重叠
document.body.appendChild(stats.dom);
function animate() {
stats.begin();
// ... 你的渲染逻辑
stats.end();
requestAnimationFrame(animate);
}
Stats显示三个指标:
- FPS:帧率,稳定60说明流畅,低于30就要优化了
- MS:每帧渲染耗时,单位毫秒。16ms以内是60帧,33ms以内是30帧
- MB:内存占用(部分浏览器支持)
我曾经接手过一个项目,FPS只有20多。用Stats一看,MS高达45ms。再细查,发现场景里有个模型面数超过100万。嗯,这就是典型的「模型太精细,渲染扛不住」。后来用Decimate减面到10万,FPS直接回到55。
4.5 综合配置示例
最后,给一个我常用的生产环境配置模板:
const renderer = new THREE.WebGLRenderer({
antialias: true,
powerPreference: 'high-performance'
});
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;
// 性能监控
const stats = new Stats();
document.body.appendChild(stats.dom);
// 渲染循环
function animate() {
stats.begin();
requestAnimationFrame(animate);
renderer.render(scene, camera);
stats.end();
}
animate();
// 窗口自适应
window.addEventListener('resize', () => {
renderer.setSize(window.innerWidth, window.innerHeight);
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
});
这个配置我用了两年多,在几十个项目里验证过。你直接拿去用,基本不会出大问题。当然,具体项目还得微调——比如做建筑可视化,阴影质量可以再高点;做移动端游戏,抗锯齿和阴影可能都得关掉。
记住一句话:渲染器配置没有银弹。理解每个参数的含义,根据你的目标设备做取舍,这才是正解。