threejs(五)丰富简易星系场景(光照+动画)

443 阅读5分钟

1. 发光的太阳

1.1 各种光照的含义

首先,我们列举出不同种类的光照及其影响。好选择对应种类的光照应用。

种类特点用途
AmbientLight 环境光为场景中所有物体提供均匀的基础光照,没有方向性用于补充其他光源,创造更自然的整体光照效果
DirectionalLight平行光模拟无限远处的光源,如太阳光,具有方向性适用于户外场景和室内大型场景的主光源,可以产生清晰的阴影
PointLight点光源模拟一个点光源,从该点向四面八方发射光线适用于室内场景的局部照明,可以产生柔和的阴影
SpotLight聚光灯模拟手电筒等聚光灯,有特定的光照范围和角度适用于模拟头灯、台灯等聚光效果,可以产生清晰的聚焦阴影
HemisphereLight半球光模拟天空和地面两个半球面的不同颜色光源适用于营造自然的环境光,如户外场景的天空和地面的光照差异
RectLight区域光模拟一个矩形区域的光源,光照更加自然和柔和适用于模拟室内的窗户、灯箱等矩形光源

1.2 添加点光源-PointLight

上一篇文章中,给太阳以及其他星球增加了贴图,但是太阳是发光的呀,这个特点要展示出来。对照上述表格,可在太阳的中心位置放置一个点光源(PointLight)。

PointLight( color : Color, intensity : Float, distance : Number, decay : Float )

  • color -(可选)一个表示颜色的 Color 的实例、字符串或数字,默认为一个白色(0xffffff)的 Color 对象。
  • intensity-(可选)光照强度。默认值为 1。
  • distance- 光源照射的最大距离。默认值为 0(无限远)。
  • decay - 沿着光照距离的衰退量。默认值为 2。
//模拟太阳发光,添加点光源
const pointLight = new THREE.PointLight(0xFFFFFF,3700);
scene.add(pointLight);

添加点光源后,可以看到以太阳为中心发射光线了。而太阳的展示必须不受到点光源的影响,所以太阳的材质可为MeshBasicMaterial不变。

light.png

1.3 添加环境光-AmbientLight

但是,星球背光的地方实在是太暗了,最好是再加上一个环境光(AmbientLight

AmbientLight( color Color, intensity : Float )

  • color-(可选)一个表示颜色的 Color 的实例、字符串或数字,默认为一个白色(0xffffff)的 Color对象。
  • intensity- (可选)光照的强度。默认值为 1
//添加强度为0.1的整体环境光
const ambientLight = new THREE.AmbientLight(0xffffff,0.1);
scene.add(ambientLight); 
light2.png

添加环境光后,即使是星球的背面,也不至于看上去漆黑一片。

2. 星球的自转与公转

2.1 自转

在我们搭建完场景之后,剩下的就是让星球们都动起来,每个星球包括太阳都要完成自转,除了太阳,其他星球要围绕着轨道进行公转。

自转比较好处理,也就是原来的物体围绕着每个轴进行旋转。

function animate() {
    requestAnimationFrame(animate);
    //太阳围绕着Z轴进行每帧0.004的速度自转
    sun.rotateZ(0.004);
    renderer.render(scene,camera);
}

2.2 公转

在处理公转之前,先仔细理解下星球自转与公转的关系。为了让每个对象的结构更加清晰,并易于操作的时候,我们引入组(Group)概念。

THREE.Group() 是 Three.js 中非常有用的一个类,它可以用来对多个 Object3D 对象进行组织和管理。下面我们来详细了解一下它的用法:

作用: THREE.Group() 可以将多个 Object3D 对象(如 MeshLightCamera 等)聚合到一个组中进行统一管理。 这样可以方便地对整个组进行位置、旋转、缩放等变换操作,而不需要逐个对每个对象进行操作。

使用方法:

//创建一个 `THREE.Group()` 对象
const group = new THREE.Group();
//将对象添加到组中
group.add(mesh1);
group.add(mesh2);
group.add(light);
//可对整个组进行变换操作
group.position.set(x, y, z);
group.rotation.set(rx, ry, rz);
group.scale.set(sx, sy, sz);
//将整个组添加到场景中
scene.add(group);

回到我们当前的案例,就可以用这种父子关系解决自转与公转的关系。在原来的函数中,我们对实例的mesh添加rotateZ方法,实现星球的自转。

function animate() {
    requestAnimationFrame(animate);
      //太阳围绕着Z轴进行每帧0.004的速度自转
    sun.rotateZ(0.004);
    mercury.mesh.rotateZ(0.004);
    venus.mesh.rotateZ(0.002);
    earth.mesh.rotateZ(0.02);
    mars.mesh.rotateZ(0.018)
    renderer.render(scene,camera);
}

实现公转,就要添加组的概念到createPlanete函数中

function createPlanete(size,radius,rotateAngle,texture) {
    //……省略没有改动的代码
    const mesh = new THREE.Mesh(geometry,material);
    const group = new THREE.Group();
    group.add(mesh);
    scene.add(group);
    
    //……省略没有改动的代码
   return { mesh,group }
}

function animate() {
    requestAnimationFrame(animate);
      //太阳围绕着Z轴进行每帧0.004的速度自转
    sun.rotateZ(0.004);
    //自转
    mercury.mesh.rotateZ(0.004);
    venus.mesh.rotateZ(0.002);
    earth.mesh.rotateZ(0.02);
    mars.mesh.rotateZ(0.018);
    //公转
    mercury.group.rotateZ(0.04);
    venus.group.rotateZ(0.015);
    earth.group.rotateZ(0.01);
    mars.group.rotateZ(0.008);
    renderer.render(scene,camera);
}

如此,便实现了自传与公转。那是否会有疑问,为啥这里的meshgroup对象的旋转围绕的中心点不一样?从group.add(mesh)这行代码看,是group这个组里面里面包含了mesh对象,group为父级整体,而mesh为子级。可以举个例子:

 const Human = new THREE.Group();
 Human.add(arm);
 Human.add(head);
 Human.add(foot);
 ……

如上述代码,定义一个人为一个组,组内包含胳膊、头、脚等等。当我们要移动这个人的位置,那只需要移动human这个父类对象,它的子集也会一起移动;当我们抬起这个人的胳膊,只需要对arm对象进行操作,其他子类对象不会受到影响。

THREE.Group() 是 Three.js 中非常有用的一个类,它可以帮助我们更好地组织和管理复杂的 3D 场景。合理使用 THREE.Group() 可以使代码更加清晰、易维护,并提高渲染性能。

3. 总结

在本篇文章中,我们主要介绍了PointLight点光源、AmbientLight环境光以及分组Group()的概念。枯燥的概念列举总是缺乏说服力,而且概念容易混淆,所以八青妹都是在一个个的案例中,摸索遇到的新的概念,进行记录和总结。学以致用,才能融会贯通。这个简易的星系,我们就介绍到这里了。这里我们将完整的星系代码奉上(为了方便访问,这里将图片压缩后转成了base64)。感兴趣的同学,可以在这个基础上,多创建一些行星,给土星增加土星环,使用的是THREE.RingGeometry()