three.js基础学习之平行光源DirectionalLight

three.js基础学习之光源的学习中,我们已经学习和知道了如何在three.js中通过构造函数创建光源,并且设置光的颜色和强度。但是,我们在案例中并没有直接使用THREE.Light进行构造,而是使用THREE.DirectionalLight。这是因为Light只是three.js中众多光源中的基类,并不能直接构造光源,构造光源还是需要具体的光源构造函数,比如我们用到的DirectionalLight。那么,现在就开始学习探索DirectionalLight,了解它的用法和使用场景。

在正式开始之前,复制three.js基础学习之光源的demo代码并创建一个新的html文件learnThree16.mtml

DirectionalLight平行光可以发射到无限远,在three.js中用来模拟太阳光。因此,在使用到太阳光的场景中,我们都可以考虑使用它。创建平行光时只需要使用THREE.DirectionalLight构造器,并传入光照颜色和强度即可。如下:

1
2
3
4
5
6
7
{
const lightColor = 0xffffff; // 灯光颜色
const intensity = 1; // 灯光强度
const light = new THREE.DirectionalLight(lightColor, intensity);
light.position.set(10,10,20);
scene.add(light);
}

这样,我一使用DirectionalLight平行光的three.js应用便创建了出来,但是如果仅仅是如此,就没必要单独拿出来讲了。接下来我们开始进一步探索DirectionalLight



让three.js中DirectionalLight平行光显示阴影,设置castShadow 属性

前面我们说了,three.js中DirectionalLight平行光是用来模仿太阳光的,但是现在我们做出来的应用中并没有阴影的存在。这是three.js为了减少对内存的消耗,提升资源的利用效率,默认情况下关闭了阴影渲染,因此需要我们去手动打开阴影渲染。

  • 首先,告诉渲染器我们需要渲染阴影
    1
    2
    const renderer = new THREE.WebGLRenderer({canvas})
    renderer.shadowMap.enabled = true; // 新增这行代码,告诉渲染器要渲染阴影了
  • 设置具体需要投射阴影的物体
    虽然我们已经告诉渲染器接下来要开始投射阴影了,但这还不够。我们还需通过将对应物体的castShadow属性值设置为true来告诉渲染器具体是哪些物体需要投射阴影。

    在我们已有的代码中,我们将立方体的castShadow属性值设置为true。

    1
    2
    const box = new THREE.Mesh(boxGeometry, material)
    box.castShadow = true // 告诉渲染器需要为这个box渲染阴影
  • 决定那些地方可以接收阴影
    现在,我们的渲染器已经会渲染阴影了,也已经知道了具体哪个物体需要渲染阴影,就缺阴影投放在哪了。这个时候,我们还需要告诉渲染引擎具体哪些地方可以接收阴影。我们需要将能接收阴影物体的receiveShadow属性值设置为true

    在我们的示例代码中,我们可以把平面设置为可以接收阴影。代码如下:

    1
    2
    const plane = new THREE.Mesh(planeGeometry, material)
    plane.receiveShadow = true; // 新增代码,让plane接收阴影
  • 最后,同意光源投射隐隐
    走完以上步骤后,我们还需要让光线自身可以投身阴影,需要将lightcastShadow 属性值设置为true。
    1
    2
    3
    const light = new THREE.DirectionalLight(lightColor, intensity);
    light.position.set(10,10,20);
    light.castShadow = true;
    现在,刷新你的代码看一下,是不是能看到像下面这样的阴影了呢?



设置three.js中DirectionalLight平行光的照射位置target

我们已经知道了如何设置光源发出的位置,现在,就来学习如何设置光源照射位置。默认情况下,无论光从哪个位置发出,最终都会照射到点(0,0,0)的位置。要想改变光照的位置,就需要设置light.target.position属性。

为了能方便的看出我们光照位置的变化,在开始之前我们先将demo中的动画停止。如下修改render函数代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function render (time) {
if (resizeRenderSizeToDisplaySize()) {
const pixelRatio = window.devicePixelRatio;
const width = canvas.clientWidth * pixelRatio
const height = canvas.clientHeight * pixelRatio
renderer.setSize(width, height, false)
camera.aspect = canvas.clientWidth / canvas.clientHeight;
camera.updateProjectionMatrix();
}

<!-- time *= 0.001
group['children'].forEach((each, index) => {
if (index !== 1) {
each.rotation.x = -time
each.rotation.y = -time
}
})
这部分代码全部注释
-->
renderer.render(scene, camera)
// requestAnimationFrame(render)注释
}
// requestAnimationFrame(render) 注释掉这行
render() // 放开注释

现在,我们得到了一帧静止画面

现在,我们设置光照目标的位置,如下:

1
2
light.target.position.set(5,4,2)
scene.add(light.target)

这里需要注意的是,一定要把light.target放到scene中,否则设置不生效

下面三个是我分别把光照目标位置设置为(1,1,1),(3,3,3),(5,5,5)后的光照阴影变化图,同时对比默认情况下的光照阴影变化,我们可以明显的看出随着光照目标变化,阴影也在不停的变化。



three.jsDirectionalLight平行光的照射位置target之让光追着目标走

three.js中DirectionalLight平行光的照射位置target不仅可以设置光照的具体位置,还可以让光照位置随着目标位置的移动而移动,只要这个对应的目标对象拥有position属性即可。

现在,我们对render函数再次进行改造,让立方体在白色平面上来回移动。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
let positionx = 0 // 新增
let direction = 'add' // 新增
function render (time) {
···
time *= 0.001
group['children'].forEach((each, index) => {
if (index !== 1) {
each.rotation.x = -time
each.rotation.y = -time
}
})
// 新增代码
if (positionx < 10 && direction === 'add') {
positionx = positionx + 0.1
} else {
direction = 'reduce'
positionx = positionx - 0.1
if (positionx < -10) {
direction = 'add'
}
}
// 新增到此为止
renderer.render(scene, camera)
requestAnimationFrame(render)
}

现在,立方体盒子就会左右滑动。

可以看到,盒子在来回的走动,影子也在变化,只是在某些地方消失了。

现在,我们修改一下代码,将光源target指向box。

1
2
light.target.position.set(5,4,2)
scene.add(light.target)

改为
1
light.target = box

需要注意,这里如果是将一个目标对象赋值给target,是不需要将light.target添加到scene中的,只需要将目标对象自身添加到scene中即可。在咱们的demo中,咱们已经通过group将box添加到scene中,因此不需要额外动手。

接下来看一下修改后的结果,可以看到随着盒子滑动,影子大小虽然在变化,但是影子一直存在,这说明咱们的光线确实一直在跟着box走。

关于three.js中平行光源DirectionalLight我们就学习到这里,以下是今天学习的所有内容的源代码,也可以点击链接https://www.91yqz.com/learnThree/learnThree16.html后右键查看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
<script>
function getSearchObj (key) {
const search = window.location.search.replace('?', '')
const list = search.split('&')
const obj = {}
for (let i = 0; i < list.length; i++) {
const eachList = list[i].split('=')
obj[eachList[0]] = eachList[1]
}
return obj[key]
}
async function main () {
const search = location.search
const canvas = document.querySelector('#three')
const renderer = new THREE.WebGLRenderer({canvas})
const needAnimation = getSearchObj('needAnimation') === 'true' ? true : false
const needRun = getSearchObj('needRun')=== 'true' ? true : false
if (getSearchObj('shadowMap') === 'true') {
renderer.shadowMap.enabled = true;
}
const fov = 75;
const aspect = 2;
const near = 1;
const far = 1000;

const camera = new THREE.PerspectiveCamera(fov, aspect, near, far)

const group = new THREE.Group()
group.position.z = -10
const lineMaterial = new THREE.LineBasicMaterial({ color: 0xffffff, linecap:'round' })
const loader = new THREE.TextureLoader()
const load = (url) => {
return new Promise((resolve, reject) => {
loader.load(url, (texture) => {
resolve(texture)
})
})
}

// {
const width = 4
const height = 4
const depth = 4
const boxGeometry = new THREE.BoxGeometry(width, height, depth)

const material = new THREE.MeshStandardMaterial({
color: 0xe74478
})

const box = new THREE.Mesh(boxGeometry, material)
box.position.set(0, 0, -5)
box.castShadow = true
group.add(box)
// }

{
const width = 40
const height = 30
const planeGeometry = new THREE.PlaneGeometry(width, height)
const material = new THREE.MeshStandardMaterial({
color: 0xffffff
})
const plane = new THREE.Mesh(planeGeometry, material)
plane.position.set(0, 0,-10)
plane.receiveShadow = true;
// plane.rotation.x = -0.2 * Math.PI
group.add(plane)
}

const scene = new THREE.Scene()

{
const lightColor = '#ffffff'; // 灯光颜色
const intensity = 1; // 灯光强度
const light = new THREE.DirectionalLight(lightColor, intensity);
light.position.set(0,10,20);
light.castShadow = true;
console.log(light)
//
// light.shadow.mapSize.width = 8
// light.shadow.mapSize.height = 9
const hadTarget = getSearchObj('hadTarget') === 'true' ? true : false
if (needRun && hadTarget) {
light.target = box
} else {
const lightTarget = getSearchObj('lightTarget')
if (lightTarget) {
const targetList = lightTarget.split(';')
light.target.position.set(targetList[0], targetList[1],targetList[2])
scene.add(light.target)
}
}

scene.add(light);


}
scene.add(group)

function resizeRenderSizeToDisplaySize () {
const pixelRatio = window.devicePixelRatio;
const displayWidth = canvas.clientWidth * pixelRatio
const displayHeight = canvas.clientHeight * pixelRatio
const renderWidth = canvas.width;
const renderHeight = canvas.height;
const needResize = displayWidth !== renderWidth || displayHeight !== renderHeight
return needResize
}
let positionx = 0
let direction = 'add'
function render (time) {
if (resizeRenderSizeToDisplaySize()) {
const pixelRatio = window.devicePixelRatio;
const width = canvas.clientWidth * pixelRatio
const height = canvas.clientHeight * pixelRatio
renderer.setSize(width, height, false)
camera.aspect = canvas.clientWidth / canvas.clientHeight;
camera.updateProjectionMatrix();
}

if (needAnimation) {
time *= 0.001
group['children'].forEach((each, index) => {
if (index !== 1) {
each.rotation.x = -time
each.rotation.y = -time
}
})
}
if (needRun) {
if (positionx < 10 && direction === 'add') {
positionx = positionx + 0.1
} else {
direction = 'reduce'
positionx = positionx - 0.1
if (positionx < -10) {
direction = 'add'
}
}
box.position.x = positionx
}
renderer.render(scene, camera)
if (needAnimation || needRun) {
requestAnimationFrame(render)
}
}
if (needAnimation || needRun) {
requestAnimationFrame(render)
} else {
render()
}
}
main()
</script>