为桌台添加材质纹理
为物体添加适当的材质纹理,可以使其视觉效果产生质的飞跃。接下来,我们将为桌台添加一种木质纹理,用到的纹理贴图来自Pixabay.com。
我们使用 TextureLoader 来加载纹理贴图,其 load
方法第1个参数为贴图的 URL 字符串,该方法返回一个纹理对象,可直接赋值给材质对象的颜色贴图属性 map
。代码实现如下:
class Table {
constructor({ width, height, depth }) {
const geometry = new THREE.BoxGeometry(width, height, depth);
// 纹理贴图
const url = 'https://cdn.pixabay.com/photo/2016/12/26/13/47/fresno-1932211_1280.jpg';
const material = new THREE.MeshLambertMaterial({
color: '#cccca6',
map: new THREE.TextureLoader().load(url) // 纹理贴图
});
return new THREE.Mesh(geometry, material);
}
}
然而,我们发现这样做并不完美:由于纹理贴图存在网络加载延时,所以在贴图加载完成前,桌台始终是黑色的,只有贴图加载完成后,桌台才一瞬间有了外观。如下图所示:
对此,我们需要在技术上做出一些改进,来解决纹理贴图加载前后变化的突兀感。这里有2种改进方案:
- 预加载,等纹理贴图加载完成后,再生成带纹理效果的桌台;
- 渐进式加载,桌台先显示默认颜色,等纹理贴图加载完成后,再附加纹理效果。
这里我们选择方案2,因为方案2不会阻塞桌台的渲染,有着更好的用户体验。渐进式加载的原理就是在贴图加载完成后,标记材质对象的 needsUpdate 属性为 true
,这样渲染器会在下一个渲染循环动态更新材质的纹理。核心代码如下:
const material = new THREE.MeshLambertMaterial({ color: '#cccca6' });
// 动态更新材质纹理
new THREE.TextureLoader().load(url, (texture) => {
material.needsUpdate = true;
material.map = texture;
});
加载效果如下图所示:
优化光照效果
在 three.js 中,反光材质的物体表面会因为光照的不同而呈现出不同的明暗效果,其中光源的强弱、照射面和光线夹角等参数都会对物体的渲染效果产生影响。目前我们的场景效果并不理想:柱杆看上去灰蒙蒙的,盘子则是透出一股廉价的塑料味,都缺乏真实感。正所谓,效果不够,光照来凑,我们来调整光源参数,优化光照效果,让场景更加自然、真实。
让我们先对 Lights
类进行改造,新增一个距离参数,作为调整光源位置的基准值。我们将桌台长度、柱杆高度和桌台宽度分别作为光源在 x、y、z 方向上的位置基准值进行传递,以便于更加精确地设置光源位置,达到更好的照明效果。
class Lights {
constructor({ directionX, directionY, directionZ }) {...}
}
const presenter = {
init() {
...
const lights = new Lights({
directionX: model.tableSize.width,
directionY: model.pillarSize.height,
directionZ: model.tableSize.depth
});
}
...
};
接下来,我们需要对之前已有的平行光源位置进行调整。为了更直观地调试光照效果,我们可以添加 DirectionalLightHelper 来帮助我们更好地观察光源位置平面和光照方向。
class Lights {
constructor({ directionX, directionY, directionZ }) {
const ambientLight = new THREE.AmbientLight('#fff', 1); // 环境光
const directLight = new THREE.DirectionalLight('#fff', 3); // 平行光
directLight.position.set(-directionX / 3, directionY * 4, directionZ * 1.5);
const directLightHelper = new THREE.DirectionalLightHelper(directLight, 1, '#f00');
return [ambientLight, directLight, directLightHelper];
}
}
经过这一步平行光源的位置调整,我们看到柱杆和盘子已经变得光滑透亮。(下图中的红线为平行光源辅助观察线)
最后我们再添加一个米黄色的聚光灯光源,中和下场景的“高冷”基调。
class Lights {
constructor(...) {
...
const spotLight = new THREE.SpotLight('#fdf4d5');
spotLight.position.set(5, directionY * 4, 0);
spotLight.angle = Math.PI / 2; // 光线照射范围角度
spotLight.power = 2000; // 光源功率(流明)
const spotLightHelper = new THREE.SpotLightHelper(spotLight, '#00f');
return [ambientLight, directLight, directLightHelper, spotLight, spotLightHelper];
}
}
完成后的效果如下图所示:(下图中的蓝线为聚光灯光源辅助观察线)
开启阴影效果
伴随着光源一起的自然是阴影,开启阴影能显著增强物体的立体效果。在现实世界中,阴影的产生需要光源、被照射物体和显示阴影的地方,这三者缺一不可。
在 three.js 中,出于性能考虑,实时渲染的阴影默认是关闭的,如果想要实现阴影效果,需要进行一番设置。与现实世界阴影的生成相似,这些设置都与光源、被照射物和阴影显示物有关,下面我们来逐一进行设置。
-
渲染器 开启阴影渲染支持
const rendererView = { init(...) { ... this.renderer.shadowMap.enabled = true; this.renderer.shadowMap.type = THREE.PCFSoftShadowMap; }, ... };
THREE.PCFSoftShadowMap
是 Three.js 中的一种阴影映射技术,它使用了 Percentage Closer Filtering (PCF) 和 Soft Shadows 技术来实现更加真实的阴影效果。PCF 技术可以减少阴影的锯齿感,Soft Shadows 技术可以让阴影边缘更加柔和。 -
光源 开启阴影投射,使被照射物体产生阴影
directLight.castShadow = true; // 为平行光源开启阴影投射
-
调整光源的 阴影相机 参数,控制阴影的渲染范围到合适大小
超出阴影相机范围的阴影不会被渲染,所以要将阴影相机的范围扩大到能完整包含柱杆和盘子。使用 CameraHelper 辅助对象可以帮助我们更好的观测阴影相机的视野范围。
directLight.shadow.camera.left = -directionX; directLight.shadow.camera.right = directionX; directLight.shadow.camera.top = directionZ; directLight.shadow.camera.bottom = -directionZ; const shadowCamera = new THREE.CameraHelper(directLight.shadow.camera);
-
允许 被照射物体 产生阴影
这里设置允许柱杆和盘子产生阴影,并且允许其他物体产生的阴影可以投射到它们表面(接收阴影)。需要注意,
castShadow
和receiveShadow
要设置到Mesh
对象上,不能设置到Group
上。/* 柱杆 */ class Pillar { constructor(...) { ... const body = new THREE.Mesh(geometry, material); body.castShadow = true; // 允许产生阴影 body.receiveShadow = true; // 允许接收阴影 ... }, ... } /* 盘子阴影设置同上 */ clas