第1章:光照与阴影——让数据“活”起来
大家好,我是老张。做3D数据可视化这些年,我最大的感触就是:没有光照的3D场景,就像没开灯的博物馆——再好的展品也看不清。
这一章,咱们聊聊光照与阴影。说白了,就是怎么给你的数据“打光”。
1.1 环境光(AmbientLight)——基础照明
环境光,你可以把它想象成阴天时的光线。它没有方向,均匀地照亮所有物体。
为什么需要它?
没有环境光,背光面会全黑。数据可视化里,我们不想丢失任何细节。
核心要点:环境光只改变亮度,不产生阴影,也没有方向感。
// 创建一个环境光,颜色偏暖,强度0.5
const ambientLight = new THREE.AmbientLight(0x404060, 0.5);
scene.add(ambientLight);
我个人习惯把环境光强度控制在0.3~0.6之间。太强了,其他光源的效果会被“冲淡”。
小技巧:环境光颜色可以跟主光源互补。比如主光源偏冷(蓝色),环境光就偏暖(橙色),这样画面更有层次。
1.2 平行光(DirectionalLight)——模拟太阳
平行光,顾名思义,所有光线都是平行的。它模拟的是太阳光。
特点:
- 有明确的方向
- 能产生清晰的阴影
- 光照强度不随距离衰减
我在项目中遇到过一个问题:用平行光给一个城市建筑群打光,结果所有楼房的阴影方向都一样,看起来特别假。后来我加了两个平行光,一个主光,一个补光,效果才自然。
// 主光源:从右上角打下来
const dirLight = new THREE.DirectionalLight(0xffffff, 1);
dirLight.position.set(10, 20, 10);
scene.add(dirLight);
// 辅助光:从左下角补光,强度低一些
const fillLight = new THREE.DirectionalLight(0xffeedd, 0.3);
fillLight.position.set(-10, 5, -10);
scene.add(fillLight);
注意:平行光的阴影范围需要手动设置。我曾经忘了设置,结果阴影只显示了一小块,排查了半天。
1.3 点光源(PointLight)——灯泡效果
点光源就像灯泡,从一个点向四面八方发光。它会产生柔和的阴影,光照强度随距离衰减。
适用场景:
- 模拟路灯、台灯
- 突出某个数据点
- 营造氛围
const pointLight = new THREE.PointLight(0xffaa00, 1, 50);
pointLight.position.set(5, 3, 5);
scene.add(pointLight);
// 可以加个小球体表示光源位置,方便调试
const sphere = new THREE.Mesh(
new THREE.SphereGeometry(0.2, 16, 16),
new THREE.MeshBasicMaterial({ color: 0xffaa00 })
);
sphere.position.copy(pointLight.position);
scene.add(sphere);
你想想看,点光源的衰减参数很重要。默认是平方衰减,有时候太猛了。我一般把距离设成场景对角线长度的1.5倍,衰减系数调成1(线性衰减),这样过渡更自然。
1.4 聚光灯(SpotLight)——舞台追光
聚光灯有方向、有角度、有衰减范围。它就像一个手电筒,只照亮一个锥形区域。
参数说明:
| 参数 | 作用 | 我的建议值 |
|---|---|---|
| angle | 光锥角度(弧度) | 0.3~0.6(约17°~34°) |
| penumbra | 边缘柔化程度 | 0.2~0.5 |
| decay | 衰减速度 | 1(线性)或2(平方) |
| distance | 最远照射距离 | 场景对角线长度 |
const spotLight = new THREE.SpotLight(0xffffff, 1);
spotLight.position.set(0, 10, 0);
spotLight.target.position.set(0, 0, 0);
spotLight.angle = 0.4;
spotLight.penumbra = 0.3;
spotLight.decay = 1;
spotLight.distance = 30;
scene.add(spotLight);
scene.add(spotLight.target);
避坑指南:我曾经把聚光灯的angle设成0.1,结果只照亮了一个小点,还以为代码写错了。后来才意识到,角度太小了。做数据可视化,聚光灯适合突出某个关键数据区域,别用它做全局照明。
1.5 阴影投射与接收——让数据有“重量”
阴影是3D场景真实感的灵魂。没有阴影,物体就像飘在空中。
三步开启阴影:
- 渲染器开启阴影:
renderer.shadowMap.enabled = true - 光源设置投射阴影:
light.castShadow = true - 物体设置投射/接收:
mesh.castShadow = true/mesh.receiveShadow = true
// 1. 渲染器
const renderer = new THREE.WebGLRenderer();
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap; // 柔和阴影
// 2. 光源
const dirLight = new THREE.DirectionalLight(0xffffff, 1);
dirLight.castShadow = true;
dirLight.shadow.mapSize.width = 1024;
dirLight.shadow.mapSize.height = 1024;
// 3. 物体
const cube = new THREE.Mesh(geometry, material);
cube.castShadow = true;
cube.receiveShadow = true;
const plane = new THREE.Mesh(planeGeo, planeMat);
plane.receiveShadow = true;
性能警告:阴影很吃性能。我做过一个项目,场景里有200多个物体都开了阴影,结果帧率掉到15fps。后来只给关键物体开阴影,其他用假阴影(一个半透明圆片)代替,帧率回到了60fps。
1.6 实战组合拳
一个典型的数据可视化场景,我通常这样配光:
- 环境光:强度0.3,颜色偏冷(0x404060)
- 主平行光:强度1.0,从右上45°打下来,开启阴影
- 补光:强度0.2,从左下打上来,不开阴影
- 点光源:放在需要突出的数据点附近,强度0.5
嗯,这里要注意:光源数量不是越多越好。我见过有人一个场景放了20个光源,结果画面又亮又平,完全没有层次感。3~5个光源足够了。
调试技巧:用lightHelper和shadowCameraHelper可视化光源位置和阴影范围。Three.js官方有示例,直接复制过来用就行。
最后说一句:光照是门艺术,不是科学。同样的数据,不同的打光方式,传达的信息完全不同。多调、多看、多对比,慢慢就有感觉了。
下一章,咱们聊聊材质和纹理——怎么让数据表面有“质感”。