Three.js-硬要自学系列23 (光源环境贴图、模拟太阳光和阴影、精灵模型,标注场景贴图、模拟粒子移动)

866 阅读6分钟

本章主要学习知识点

  • 了解环境光贴图,并练习如何使用
  • 了解什么事精灵模型
  • 使用精灵模型实现标注场景贴图
  • 掌握如何使用精灵模型模拟粒子移动

光源环境贴图

光源环境贴图可以理解为一种让物体表面「反射周围环境」的光照技术。它通过一张(或一组)贴图模拟真实世界中物体对周围环境(如天空、地面、建筑)的光线反射效果,使3D场景更逼真。

你可以想象你的手里有一个金属球,把它放在草地上时,球的表面会反射周围的草地、天空和树木。环境贴图就是给这个金属球「贴」上一组周围环境的图片,让它的表面能反射出这些画面。

两种实现环境贴图的方式

  • 立方体贴图 (CubeMap)
    • 用6张图片分别表示上下、前后、左右六个方向的环境(类似一个立方体盒子包裹住场景)。
  • HDR贴图 (高动态范围图)
    • HDR贴图能存储更丰富的亮度和颜色信息,适合模拟真实光照环境。

环境贴图与光源的关系

  • 作为环境光AmbientLight的补充
    • 环境光只能均匀照亮物体,而环境贴图能根据物体表面角度动态反射周围颜色,增强真实感
    • 例如:金属材质在环境贴图下会有动态高光,而纯环境光只能让物体呈现单一颜色
  • 间接光源效果
    • 环境贴图可以被视为一种「间接光源」。例如,贴图中的明亮区域(如天空)会让物体表面更亮,暗部区域(如地面阴影)则减少反射亮度

上手实践下

创建一个立方体纹理贴图加载器用来加载图片素材

const textureCube = new THREE.CubeTextureLoader()
.setPath('texture/environmentMaps/1/')
.load([
    'px.jpg','nx.jpg','py.jpg','ny.jpg','pz.jpg','nz.jpg'
])

导入模型,并设置材质为立方体纹理贴图,贴图强度为1

// 设置子对象的材质的环境贴图
child.material.envMap = textureCube;
// 设置子对象的材质的环境贴图强度
child.material.envMapIntensity = 1;
child.material.needsUpdate = true;

32.gif

旋转模型,我们就能在玻璃中看到环境贴图的背景,这里要注意添加光源哦,光源负责直接照明,环境贴图负责间接反射。

模拟太阳光和阴影

在three.js中模拟太阳光和阴影主要依靠平行光(DirectionalLight) 和阴影系统配合实现

太阳光的本质——平行光

平行光可以理解为无限远的光源,就像现实中的太阳光,所有光线都朝同一个方向平行照射。它的特点如下:

  • 可以设置照射方向(如position.set(0, 100, 0)表示从天空垂直向下照
  • 能产生清晰的投影(比如树木在地面的影子)

总结阴影生效的4个关键设置

1.开启渲染器阴影计算
renderer.shadowMap.enabled = true; //告诉Three.js 要渲染阴影
2.光源投射阴影
sunLight.castShadow = true; //让平行光产生阴影
3.物体投射/接收阴影
cube.castShadow = true; //立方体能投射影子 
plane.receiveShadow = true; //地面能接收影子
4.优化阴影质量
sunLight.shadow.mapSize.width = 2048; //阴影贴图分辨率越高越清晰 
sunLight.shadow.camera.near = 5; //阴影计算最近距离 
sunLight.shadow.camera.far = 200; //阴影计算最远距离

这里我做了一个示例,调整平行光位置,阴影同步改变

3.gif

值得注意的点

  • 支持材质MeshStandardMaterialMeshPhongMaterial等(如金属车身材质)
  • 不支持材质MeshBasicMaterial(基础材质无光影效果)

精灵模型Sprite

精灵模型(Sprite) 可以理解为一种特殊的“二维贴纸”,它能将图片或文字贴在3D场景中,但始终正对摄像机,就像现实中的广告牌或舞台演员始终面向观众一样。

精灵模型有以下几个特点

  • 永远面向你
    无论你如何旋转3D场景,精灵模型始终正对屏幕,就像一张悬浮的2D纸片
  • 轻量级性能
    相比复杂3D模型,精灵仅需一个平面和一张贴图,适合大量使用(如雨滴、星星等粒子效果)
  • 动态内容支持
    可以用图片、Canvas绘制的文字(如标签)或动态视频作为贴图,灵活性高

适用场景

  • 信息标签
    给3D模型添加浮动文字说明(如设备名称、温度数值)

  • 粒子效果

    • 雨雪效果:用大量雨滴/雪花贴图的精灵模拟天气
    • 火焰烟雾:通过半透明贴图实现动态粒子
  • UI元素
    在3D场景中叠加按钮、指示图标,且不受视角影响

  • 性能优化场景
    当需要渲染数千个相同物体(如森林中的树木)时,用精灵代替3D模型可大幅提升性能。

创建一个简单的精灵模型

const spriteMaterial = new THREE.SpriteMaterial({
    color: '#373737',
    rotation: Math.PI / 4   // 旋转精灵
});
const sprite = new THREE.Sprite(spriteMaterial);
sprite.scale.set(5, 5, 1);
scene.add(sprite);

image.png

需要注意的点

  • 透明贴图处理
    若使用PNG透明贴图,需设置材质属性transparent: true,避免出现黑边

  • 性能优化技巧

    • 大量重复精灵可使用THREE.InstancedMesh批量渲染
    • 控制贴图分辨率,避免过大导致内存浪费
  • 大小控制
    通过.scale属性调整精灵尺寸,但透视相机会导致距离越远精灵越小(可通过正交相机规避)

精灵模型标注场景贴图

精灵模型(Sprite)标注场景贴图 可以理解为一种“智能标签”技术,它能将文字或图标贴在3D场景中的特定位置,并且始终正对屏幕,就像现实中的指示牌一样。

我们通过textureLoader加载纹理贴图,并将贴图应用在精灵材质上

const textureLoader = new THREE.TextureLoader();
const spriteMap = textureLoader.load('texture/environmentMaps/0/pz.jpg');
const spriteMaterial = new THREE.SpriteMaterial({ map: spriteMap }); 
const sprite = new THREE.Sprite(spriteMaterial);
sprite.scale.set(1, 1, 1);
sprite.position.set(0, 2 + 1/2, 0);
scene.add(sprite);

231.gif

需要注意的是,如果我们加载的贴图为png,则需要开启支持透明背景 transparent: true

精灵模型模拟粒子移动

设置精灵模型材质贴图,用来模拟雨滴

const texture = new THREE.TextureLoader().load('texture/particles/8.png')
const spriteMaterial = new THREE.SpriteMaterial({map: texture})

批量创建精灵,并随机它们的位置

const group = new THREE.Group()
for(let i = 0; i < 1000; i++) {
    const sprite = new THREE.Sprite(spriteMaterial)
    group.add(sprite)
    sprite.scale.set(0.5, 0.5, 0.5)
    sprite.position.set(
       1000*(Math.random()-0.5),
       600*Math.random(),
       1000*(Math.random()-0.5)
    )
    scene.add(group)
}

改变精灵模型的位置,模拟下雨的赶脚

const clock = new THREE.Clock();
function loop() {
    const t = clock.getDelta();
    group.children.forEach((sprite, index) => {
        // 将精灵的y坐标减去20*t
        sprite.position.y -= 60*t
        // 如果精灵的y坐标小于-100
        if(sprite.position.y < -100) {
            // 将精灵的y坐标设置为400
            sprite.position.y = 400;
        }
    })
    requestAnimationFrame(loop)
}

效果如下

23.gif

以上案例均可在案例中心查看体验

THREE 案例中心

image.png