世界如果没有太阳, 那所有的一切都将黯然失色 --- 佚名
概述
在THREEjs的世界里, 如果没有光源, 那么所有的材质都无法显示, 场景也将不可见.THREEjs本身支持大量的光源. 这篇文章将尽我所能阐述清楚这些光源的不同用法, 包括:
- 有哪些可用的光源
- 使用的时机
- 调整和配置光源的行为
- 创建光晕
不同种类的光源
- AmbientLight: 基本光源, 光源的颜色会叠加在场景现有物体上
- PointLight: 点光源, 无法创建阴影
- SpotLight: 聚光光源, 类似台灯, 天花板的吊灯或手电筒, 可以创建阴影
- DirectionalLight: 无限光, 类似太阳的平行光, 可以创建阴影
- HemisphereLight: 特殊光源, 模拟反光面和光线微弱的天空来创建更自然的室外光线. 无法创建阴影
- AreaLight: 与SpotLight相反, 创建一个发散光线的平面, 无法创建创建阴影
- LensFlare: 不是光源, 但可以添加光晕
看一下对比图, 初中课本里面就有:
基础光源
AmbientLight
一般情况下不能将此光源作为场景中的唯一光源, 因为会将场景中所有的物体渲染成一个颜色, 显然不满足需求.使用其他光源, 可以添加阴影以及额外的颜色, 注意在使用AmbientLight的时候要使用感光的材质, 如果使用基本材质是不会对Ambient光源反应的.
const scene = new THREE.Scene()
const camera = new THREE.PerspectiveCamera(45, window.innerWidth/window.innerHeight, 0.1, 1000)
camera.position.set(30, 30, 50)
const planeGeometry = new THREE.PlaneGeometry(30, 30)
const planeMaterial = new THREE.MeshPhongMaterial({color: 'grey'})
const plane = new THREE.Mesh(planeGeometry, planeMaterial)
plane.receiveShadow = true
plane.rotation.x = -0.5 * Math.PI
plane.position.x = 0
plane.position.y = 0
plane.position.z = 0
const axes = new THREE.AxesHelper(20)
scene.add(axes)
scene.add(plane)
// 创建ambient光源
const ambientLight = new THREE.AmbientLight('#606008', 1)
scene.add(ambientLight)
// 创建点光源增强效果
const spotLight = new THREE.SpotLight(new THREE.Color(0xcfcfcf), 1, 180, Math.PI / 4)
spotLight.castShadow = true
spotLight.position.set(-30, 10, -10)
spotLight.shadow.mapSize.set(2048, 2048)
scene.add(spotLight)
// 添加一个立方体观察效果
const cube = new THREE.Mesh(new THREE.BoxGeometry(5, 5, 5), new THREE.MeshPhongMaterial({color: 'blue'}))
cube.position.set(5, 0, 5)
cube.castShadow = true
cube.receiveShadow = true
scene.add(cube)
camera.lookAt(new THREE.Vector3(0, 0, 0))
const renderer = new THREE.WebGLRenderer()
renderer.setClearColor(new THREE.Color('grey'))
renderer.setSize(window.innerWidth, window.innerHeight)
document.getElementById('webgl-output').appendChild(renderer.domElement)
renderer.shadowMapEnabled = true
const render = function() {
renderer.render(scene, camera)
requestAnimationFrame(render)
}
render()
// 设置dat.gui
setupControl(ambientLight, spotLight)
以下的gif图中可以直观看出AmbientLight颜色的设置, intensity的设置对整个renderer的影响, 通关开关spotLight也可以看出仅仅只有AmbientLight的话是非常暗淡和单调的, 有了spotlight, 并且设置了应用, 整个场景才更加自然.

AmbientLight的配置相对简单, 只有两个参数color和intensity, 对应颜色和强度.可以看到这个颜色被全局应用, 应尽量保守, 如果颜色过于明亮, 会发现颜色过于饱和.
更值得说的是SpotLight这样一种光源, 看看PointLight,DirectionLight他们之间的区别.

- PointLight是从中央某一点向360°发射光线
- SpotLight是从某一个点发出一个锥形的光线, 所以在之前的
new THREE.SpotLight(new THREE.Color(0xcfcfcf), 1, 180, Math.PI / 4)中的最后一个参数Math.PI / 4就是表示这个夹角的. - 那么平行光就是从一个平面发出的光, 光线彼此平行, 还记得物理课上有说太阳虽然是点光源, 但是发射到地球上时已经很远了, 可以近似看成一个平行光
下面将一一介绍
预定义两个常量, 会在接下来的光源中用到
const position = new THREE.Vector3(-30, 10, -10)
const color = 0xfffffff
PointLight
可以看一下例子, 需要注意的是, 点光源尽量不要去产生阴影, 因为方向是360°的, 计算阴影对GPU消耗很大.
export const pointLight = new THREE.PointLight(color, 1, 180)
pointLight.position.add(position)


intensity在AmbientLight的时候就已经介绍过了, 表示光的强度, 这里主要介绍一下distance, 它表示的是光线强度会随着距离的增加而减弱, 到指定的距离, 本例是180. 默认值为0, 即光线不会随着距离的增加而又任何的更改
SpotLight
SpotLight是最常使用的一种光源之一, 特别是在需要创建一个阴影的时候, 在最初介绍AmbientLight的时候就已经有它的身影了.
export const spotLight = new THREE.SpotLight(color, 1, 180, Math.PI / 4)
// 一定设置此属性才能产生阴影
spotLight.castShadow = true
spotLight.position.set(-30, 10, -10)
spotLight.shadow.mapSize.set(2048, 2048)
spotLight.target = plane
scene.add(spotLight)
其实跟PointLight的使用方式有很多的相似之处, 比如创建的时候前三个参数都是颜色, intensity, distance, 第四个前面有说到就是光锥的角度.
还有其他特殊的属性,
- castShadow 需要阴影.
- target 光源始终会指向的一个目标, 即使这个目标是动态的, 上例中是将其指向了plane.
- exponent 光强衰减指数, 光强中心向锥形边缘递减的的速度
- 与阴影相关
-
mapSize 用于产生阴影的像素多少, 越多越平滑, 默认值为512.
-
onlyShadow 不会有任何光源的添加, 只会有阴影
-
shadowCameraFar 投影远端, 距离光源哪一个位置可以生成阴影, 默认5000
-
shadowCameraNear 投影近点, 距离光源哪一个位置可以开始生成阴影, 默认50
-
shadowCameraFov 投影视场, 默认50
-
shadowCameraVisible 投影方式是否可见 -- 该方法已被修改为
new THREE.CameraHelper( spotLight.shadow.camera ), 可直观的看出光源照射范围
-
shadowDarkness 投影暗度, 默认为0.5 通常情况下与阴影相关的属性不需要额外设置, 使用默认值即可,如果有特殊需求可自定义.
-
下面是SpotLight的演示动画, 通过更改不同属性达到不同的阴影效果, 关于camera的一些属性, 比如fov-视场等, 在之前camera的部分中有讲到

DirectionalLight
平行光与聚光等那样光强跟物体距离光源有关, 比如有distance和exponent来进行设置, 只要在平行光照射的范围之内, 都是一样的光强.

export const directionalLight = new THREE.DirectionalLight(color, 2)
directionalLight.position.add(position)
directionalLight.shadowCameraNear = 1
directionalLight.shadowCameraFar = 50
directionalLight.shadowCameraBottom = -10
directionalLight.shadowCameraLeft = -10
directionalLight.shadowCameraRight = 10
directionalLight.shadowCameraTop = 10
directionalLight.castShadow = true
设置的上下左右前后近远都是设置可以产生阴影的一个正方体, 在这个正方体外不会产生任何阴影

特殊光源
HemisphereLight
可以创建更贴近自然的户外光照, 如果不使用这个光源, 也可以通过ambientLight + directional Light来模拟太阳光, 这样的光照看起来不太自然, 在户外并不是所有光照都来自上发过, 还有大气的散射以及其他物体的反射.创建十分简单
// skyColor, groudColor, intensity
export const hemisphereLight = new THREE.HemisphereLight(0xcccccc, 0x0000ff, 1)
hemisphereLight.position.set(0, 50, 0)

const grassTexture = new THREE.TextureLoader().load(require('../assets/textures/ground/grasslight-big.jpg'))
grassTexture.wrapS = THREE.RepeatWrapping
grassTexture.wrapT = THREE.RepeatWrapping
grassTexture.repeat.set(2, 2)
const planeMaterial = new THREE.MeshLambertMaterial({map: grassTexture})
AreaLight
AreaLight可以定义一个长方形的发光区域.需要注意的是
- 不支持阴影
- 只有
MeshStandardMaterial和MeshPhysicalMaterial这两种材质被支持 - 需要引入
RectAreaLightUniformsLib并且init()
RectAreaLightUniformsLib的描述是:
Real-Time Polygonal-Light Shading with Linearly Transformed Cosines
翻译过来应该就是: 运用线性余弦变换的实时多边形光照着色
AreaLight 默认是产生一个水平面, 如果有需要可以对其进行设置翻转
export const areaLight = new THREE.RectAreaLight(0xffffff, 1, 10, 10)
areaLight.position.set(0, 0.1, 0) // 故意里plane很近, 可以看出矩形
areaLight.lookAt(0, 0, 0)
areaLight.rotation.set(-Math.PI/2, 0, 0)

lens flare 镜头光晕
镜头光晕就是平时拍照如果对着太阳时, 在某个角度发生光的折射产生圆形光斑, 也就是光晕.
还是很漂亮的

生成光晕的代码跟之前也很类似,首先加载纹理, 添加到lensflare对象中, 再放到指定位置, 比如spotLight或pointLight处即可
import 'three/examples/js/objects/Lensflare'
const textureLoader = new THREE.TextureLoader()
const textureFlare0 = textureLoader.load(require('../assets/textures/flares/lensflare0.png'))
const textureFlare1 = textureLoader.load(require('../assets/textures/flares/lensflare1.png'))
const textureFlare2 = textureLoader.load(require('../assets/textures/flares/lensflare2.png'))
const color = new THREE.Color(0xffaacc)
export const lensFlare = new THREE.Lensflare()
lensFlare.addElement(new THREE.LensflareElement(textureFlare0, 512, 0, color))
lensFlare.addElement(new THREE.LensflareElement(textureFlare1, 512, 0, color))
lensFlare.addElement(new THREE.LensflareElement(textureFlare2, 60, 0.6, color))
总结
这部分都是对光源的描述, 如何新建, 配置, 阴影的, 种类包括
-
AmbientLight 全局光源
-
PointLight 点光源
-
SpotLight 锥形光源
-
DirectionalLight 方向性光源
-
HemisphereLight 户外自然光
-
AreaLight 平面光源
-
LensFlare 光晕
可以看到代码中除了光源之外还有很多跟material相关东西, 不同material对光源的反应是不同的, 下一部分将会谈到 材质