对于从入门three.js到做出3d地球这件事(第三篇: 光与影)

本篇介绍

     通过后面几篇咱们理解了坐标系、相机、物体等概念, 这一篇咱们要让3d世界里的物体, 更像咱们的事实世界的物体, 咱们要为3d世界绘制光与影。

1. 高级资料

     如果你看过前两篇文章, 你会发现在生成物体材质的时候咱们用的是MeshBasicMaterial, basic这单词意思是根本的,那也就是说与其绝对还会有高级属性, MeshLambertMaterial就是高级属性中的一种。

     应用这个属性创立进去的物体, 会产生黯淡不光洁的外表(你能够了解为须要光照时, 它的色彩才会被看到), 本篇咱们一起看看它的神奇之处。

2. 物体、墙面、高空

     绘制光源之前, 咱们先搭建一套环境, 这个环境很简略有物体、墙面、高空, 咱们通过上一篇曾经学过如何绘制一个长方体, 那么咱们就以薄薄的长方体作为墙面, 最终成果如下。

     物体、墙面、高空他们身上会有辅助线, 这个是应用的:

const edges = new THREE.BoxHelper(cube, 0x00000);scene.add(edges);
  1. BoxHelper给立方体设置边框。
  2. cube须要设置边框的物体, 前面紧跟着边框的色彩
  3. edges将实例放入场景中。

全副代码如下(../utils/OrbitControls.js的内容在我笔记里):

<html><body>    <script src="https://cdn.bootcdn.net/ajax/libs/three.js/r122/three.min.js"></script>    <script src="../utils/OrbitControls.js"></script>    <script>        const scene = new THREE.Scene();        const camera = new THREE.PerspectiveCamera(35, window.innerWidth / window.innerHeight, 1, 1000);        camera.position.z = 40;        const renderer = new THREE.WebGLRenderer();        renderer.setSize(window.innerWidth, window.innerHeight);        renderer.setClearColor(0xffffff)        orbitControls = new THREE.OrbitControls(camera, renderer.domElement);        document.body.appendChild(renderer.domElement);        const cube = initCube({            color: 'red',            len: [1, 2, 3],            position: [-0.5, 5, 1.5]        })        const wall = initCube({            color: 'gray',            len: [0.1, 10, 20],            position: [-10.1, 5, 0]        })        const land = initCube({            color: 'gray',            len: [20, 0.1, 20],            position: [0, 0, 0]        })        scene.add(cube);        scene.add(wall);        scene.add(land);        var animate = function () {            requestAnimationFrame(animate);            renderer.render(scene, camera);        };        animate();        function initCube(options) {            const geometry = new THREE.BoxGeometry(...options.len);            const material = new THREE.MeshBasicMaterial({ color: options.color });            const cube = new THREE.Mesh(geometry, material);            cube.position.add(new THREE.Vector3(...options.position))            scene.add(new THREE.BoxHelper(cube, 0x00000));            return cube        }    </script></body></html>

代码与之前几篇的代码没什么区别, 就是封装了一个initCube办法来创立立方体。

当咱们把代码里的MeshBasicMaterial替换为``时如图:

3. AmbientLight 自然光 or 环境光

    咱们的第一个配角终于退场了, 上面介绍把光源退出场景的办法。

const light = new THREE.AmbientLight('blue');scene.add(light)
  1. new THREE.AmbientLight('blue')生成实例时传入光的色彩, 下面是蓝色的光。
  2. 放入场景中。

成果就变成了上面怪异的样子:

高空与墙壁变为了蓝色, 然而在蓝色的光照耀下红色的立方体却是彩色的。

光的色彩合乎物理学

红色的物体不能反射蓝色的光, 灰色的物体却能反射蓝色的光。

  1. 自然光合乎物理学, 不好计算。
  2. 天然光源没有特地的起源方向,不会产生暗影。
  3. 不能将其作为场景中惟一的光源, 但能够配合其余光源, 起到弱化暗影或给场景增加一些额定的色彩的作用。
  4. 自然光不须要指定地位它会利用到全局。

咱们应用红光的时候:

所以要记住, 一些文章说与自然光色彩不同的物体都变为彩色是错的!!!

4. PointLight点光源

     顾名思义他是一个光点, 有人把它比喻成引火虫或是小灯泡, 它向五湖四海发射光辉, 光源自身是不可见的所以在咱们绘制的时候会在点光源的地位搁置一个立方体示意其地位信息。

const light = new THREE.PointLight('white');light.intensity = 1.8;light.distance = 30;light.position.set(2, 8, -5);scene.add(light)

点光源的属性介绍:

  1. intensity光强, 想要成为最亮的星。
  2. distance光源照耀的间隔, 默认值为0也就是有限。
  3. visible布尔值, 是否关上光源。
  4. decay衰减值, 越大衰减速度越快。

面上代码的成果如图:

换个角度看看:

当咱们把光强加大到3, 显著能够看到区别:

点光源照耀五湖四海, 如果生成暗影的话计算量太大, 所以不倡议开启暗影。

全副代码:

<html><body>    <script src="https://cdn.bootcdn.net/ajax/libs/three.js/r122/three.min.js"></script>    <script src="../utils/OrbitControls.js"></script>    <script>        const scene = new THREE.Scene();        const camera = new THREE.PerspectiveCamera(35, window.innerWidth / window.innerHeight, 1, 1000);        camera.position.z = 40;        const renderer = new THREE.WebGLRenderer();        renderer.setSize(window.innerWidth, window.innerHeight);        renderer.setClearColor(0xffffff)        orbitControls = new THREE.OrbitControls(camera, renderer.domElement);        document.body.appendChild(renderer.domElement);        const cube = initCube({            color: 'red',            len: [1, 2, 3],            position: [-0.5, 5, 1.5]        })        const wall = initCube({            color: 'gray',            len: [0.1, 10, 20],            position: [-10.1, 5, 0]        })        const land = initCube({            color: 'gray',            len: [20, 0.1, 20],            position: [0, 0, 0]        })        scene.add(cube);        scene.add(wall);        scene.add(land);        const light = new THREE.PointLight('white');        light.intensity = 3; // 光强        light.distance = 30; // 衰减间隔        light.position.set(2, 8, -5);        scene.add(light)        const edges = initCube({            color: 'red',            len: [0.2, 0.2, 0.2],            position: [2, 8, -5]        })        scene.add(edges);        const animate = function () {            requestAnimationFrame(animate);            renderer.render(scene, camera);        };        animate();        function initCube(options) {            const geometry = new THREE.BoxGeometry(...options.len);            const material = new THREE.MeshLambertMaterial({ color: options.color });            const cube = new THREE.Mesh(geometry, material);            cube.position.add(new THREE.Vector3(...options.position))            scene.add(new THREE.BoxHelper(cube, 0x00000));            return cube        }    </script></body></html>

5. 生成影子的定义

     想要生成影子可不是那么简略的, 因为可想而知在数学方面影子的计算量必然很大的, 在three.js中物体是否能够显示影子是须要独自定义的。

第一步: 渲染器反对

如果应用的WebGLRender 渲染器, 须要如下开启渲染器反对。

const renderer = new THREE.WebGLRenderer();renderer.shadowMap.enabled = true;
第二步: 为设置可生成暗影属性
light.castShadow = true;
第三步: 为物体设置可生成暗影属性
cube.castShadow = true;
第四步: 为物体设置可接管暗影属性
cube.receiveShadow = true;

这里留神了, 比如说a物体产生暗影, 暗影映在b物体上, 那么a与b都要设置上述的属性。

6. SpotLight 聚光灯(有方向的光)

     这个光源是有方向的, 也就是说他能够指定照向谁, 并且能够产生暗影。

let light = new THREE.SpotLight("#ffffff");    light.position.set(1, 1, 1);    light.target = cube    scene.add(light);

可配置的属性与下面的根本类似, 多了一个target:
target指定照谁, target必须是一个THREE.Object3D对象, 所以咱们常常会先创立一个Object3D对象, 让它不可见而后光源就能够通过照耀它, 从而实现任意方向。

咱们先看一下光源在上方照耀, 下方物体产生暗影的成果:

7. SpotLight 模仿手电(锥形光)

    开发中咱们会用SpotLight模仿手电与灯光, 能够利用他的angle角度属性。

const light = new THREE.SpotLight("#ffffff");scene.add(light);

当咱们把背景色彩换成彩色的成果:

上图就如同黑夜里手电照耀的成果了。

为了不便调试聚光灯,官网给了咱们专属的辅助线。
const helper = new THREE.CameraHelper(light.shadow.camera);    scene.add(helper);

上面咱们标注一下都有哪些知识点:


    重要的是有了这些辅助线咱们就晓得如何优化本人的我的项目了, 比方减小光源的远立体。

残缺代码

<html><body>    <script src="https://cdn.bootcdn.net/ajax/libs/three.js/r122/three.min.js"></script>    <script src="../utils/OrbitControls.js"></script>    <script>        const scene = new THREE.Scene();        const camera = new THREE.PerspectiveCamera(35, window.innerWidth / window.innerHeight, 1, 1000);        camera.position.z = 40;        const renderer = new THREE.WebGLRenderer();        renderer.setSize(window.innerWidth, window.innerHeight);        renderer.setClearColor(0xffffff)        renderer.shadowMap.enabled = true;        orbitControls = new THREE.OrbitControls(camera, renderer.domElement);        document.body.appendChild(renderer.domElement);        const cube = initCube({            color: 'red',            len: [3, 1, 3],            position: [0, 2, 0]        })        const wall = initCube({            color: 'gray',            len: [0.1, 10, 20],            position: [-10.1, 5, 0]        })        const land = initCube({            color: 'gray',            len: [20, 0.1, 20],            position: [0, 0, 0]        })        scene.add(cube);        scene.add(wall);        scene.add(land);        const arr = [8, 8, 0]        const light = new THREE.SpotLight("#ffffff", 1);        light.intensity = 2.5;        light.position.set(...arr);        light.castShadow = true;        light.target = cube        light.decay = 2;        light.distance = 350;        light.angle = Math.PI / 5        light.penumbra = 0.05;        scene.add(light);        // 聚光灯助手        const helper = new THREE.CameraHelper(light.shadow.camera);        scene.add(helper);        const edges = initCube({            color: 'red',            len: [0.2, 0.2, 0.2],            position: [...arr]        })        scene.add(edges);        const animate = function () {            requestAnimationFrame(animate);            renderer.render(scene, camera);        };        animate();        function initCube(options) {            const geometry = new THREE.BoxGeometry(...options.len);            const material = new THREE.MeshLambertMaterial({ color: options.color });            const cube = new THREE.Mesh(geometry, material);            cube.castShadow = true;            cube.receiveShadow = true;            cube.position.add(new THREE.Vector3(...options.position))            scene.add(new THREE.BoxHelper(cube, 0x00000));            return cube        }    </script></body></html>

8. DirectionalLight 平型光

     常常被举例子的就是太阳光, 实际上太阳光也不是平行的, 只是间隔太远了简直能够算是平行。
     这个光源与其余的不同的点是, 他它所照耀的区域接管到的光强是一样的。

const light = new THREE.DirectionalLight("#ffffff");scene.add(light);

介绍几个新属性:

light.shadow.camera.near = 5; //产生暗影的最近间隔light.shadow.camera.far = 50; //产生暗影的最远距离light.shadow.camera.left = -3; //产生暗影间隔地位的最右边地位light.shadow.camera.right = 3; //最左边light.shadow.camera.top = 3; //最上边light.shadow.camera.bottom = -3; //最上面

通过上图咱们能够得悉, 这个光源是齐全平行的。

残缺代码如下:

<html<body>    <script src="https://cdn.bootcdn.net/ajax/libs/three.js/r122/three.min.js"></script>    <script src="../utils/OrbitControls.js"></script>    <script>        const scene = new THREE.Scene();        const camera = new THREE.PerspectiveCamera(35, window.innerWidth / window.innerHeight, 1, 1000);        camera.position.z = 40;        const renderer = new THREE.WebGLRenderer();        renderer.setSize(window.innerWidth, window.innerHeight);        renderer.setClearColor(0x000)        renderer.shadowMap.enabled = true;        orbitControls = new THREE.OrbitControls(camera, renderer.domElement);        document.body.appendChild(renderer.domElement);        const cube = initCube({            color: 'red',            len: [3, 1, 3],            position: [0, 2, 0]        })        const wall = initCube({            color: 'gray',            len: [0.1, 10, 20],            position: [-10.1, 5, 0]        })        const land = initCube({            color: 'gray',            len: [20, 0.1, 20],            position: [0, 0, 0]        })        scene.add(cube);        scene.add(wall);        scene.add(land);        const light = new THREE.DirectionalLight("#ffffff");        light.intensity = 1.5;        light.position.set(8, 8, 0);        light.castShadow = true;        light.target = cube        light.shadow.camera.near = 5; //产生暗影的最近间隔        light.shadow.camera.far = 50; //产生暗影的最远距离        light.shadow.camera.left = -3; //产生暗影间隔地位的最右边地位        light.shadow.camera.right = 3; //最左边        light.shadow.camera.top = 3; //最上边        light.shadow.camera.bottom = -3; //最上面        scene.add(light);        // 聚光灯助手        const helper = new THREE.CameraHelper(light.shadow.camera);        scene.add(helper);        const animate = function () {            requestAnimationFrame(animate);            renderer.render(scene, camera);        };        animate();        function initCube(options) {            const geometry = new THREE.BoxGeometry(...options.len);            const material = new THREE.MeshLambertMaterial({ color: options.color });            const cube = new THREE.Mesh(geometry, material);            cube.castShadow = true;            cube.receiveShadow = true;            cube.position.add(new THREE.Vector3(...options.position))            scene.add(new THREE.BoxHelper(cube, 0x00000));            return cube        }    </script></body></html>

9. 光源要配合应用

     个别状况下不会只有繁多光源, 比方咱们会先放一个环境光, 而后在灯的模型中放上其余光源, 一些rpg游戏会用聚光灯解决用户视角。

     咱们能够同时应用多个光源, 利用gui.js查看各种壮丽的成果, 比方咱们能够用束平型光模仿舞台效果。

end.

下章会从绘制一个木块开始, 最初绘制一个贴图地球, 这次就是这样心愿与你一起提高。