關於從入門three.js到作出3d地球這件事(第三篇: 光與影)

關於從入門three.js到作出3d地球這件事(第三篇: 光與影)

本篇介紹

     經過前面幾篇咱們瞭解了座標系、相機、物體等概念, 這一篇咱們要讓3d世界裏的物體, 更像咱們的現實世界的物體, 咱們要爲3d世界繪製光與影。html

1. 高級材料

     若是你看過前兩篇文章, 你會發如今生成物體材質的時候咱們用的是MeshBasicMaterial, basic這單詞意思是基本的,那也就是說與其相對還會有高級屬性, MeshLambertMaterial就是高級屬性中的一種。ajax

     使用這個屬性建立出來的物體, 會產生暗淡不光亮的表面(你能夠理解爲須要光照時, 它的顏色纔會被看到), 本篇咱們一塊兒看看它的神奇之處。segmentfault

2. 物體、牆面、地面

     繪製光源以前, 咱們先搭建一套環境, 這個環境很簡單有物體、牆面、地面, 咱們經過上一篇已經學過如何繪製一個長方體, 那麼咱們就以薄薄的長方體做爲牆面, 最終效果以下。
image.pngapp

     物體、牆面、地面他們身上會有輔助線, 這個是使用的:dom

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方法來建立立方體。ui

當咱們把代碼裏的MeshBasicMaterial替換爲``時如圖:
image.pngspa

3. AmbientLight 天然光 or 環境光

    咱們的第一個主角終於登場了, 下面介紹把光源加入場景的方法。.net

const light = new THREE.AmbientLight('blue');
scene.add(light)
  1. new THREE.AmbientLight('blue')生成實例時傳入光的顏色, 上面是藍色的光。
  2. 放入場景中。

效果就變成了下面怪異的樣子:
image.png3d

地面與牆壁變爲了藍色, 可是在藍色的光照耀下紅色的立方體倒是黑色的。

光的顏色符合物理學

紅色的物體不能反射藍色的光, 灰色的物體卻能反射藍色的光。

  1. 天然光符合物理學, 很差計算。
  2. 天然光源沒有特別的來源方向,不會產生陰影。
  3. 不能將其做爲場景中惟一的光源, 但能夠配合其餘光源, 起到弱化陰影或給場景添加一些額外的顏色的做用。
  4. 天然光不須要指定位置它會應用到全局。

咱們使用紅光的時候:
image.png

因此要記住, 一些文章說與天然光顏色不一樣的物體都變爲黑色是錯的!!!

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衰減值, 越大衰減速度越快。

面上代碼的效果如圖:
image.png
換個角度看看:
image.png
當咱們把光強加大到3, 明顯能夠看到區別:
image.png

點光源照耀四面八方, 若是生成陰影的話計算量太大, 因此不建議開啓陰影。

所有代碼:

<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對象, 讓它不可見而後光源就能夠經過照射它, 從而實現任意方向。

咱們先看一下光源在上方照射, 下方物體產生陰影的效果:
image.png

7. SpotLight 模擬手電(錐形光)

    開發中咱們會用SpotLight模擬手電與燈光, 能夠利用他的angle角度屬性。

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

image.png

當咱們把背景顏色換成黑色的效果:
image.png
上圖就如同黑夜裏手電照射的效果了。

爲了方便調試聚光燈,官方給了咱們專屬的輔助線。
const helper = new THREE.CameraHelper(light.shadow.camera);
    scene.add(helper);

image.png

下面咱們標註一下都有哪些知識點:
image.png
image.png
    重要的是有了這些輔助線咱們就知道如何優化本身的項目了, 好比減少光源的遠平面。

完整代碼

<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; //最下面

image.png

經過上圖咱們能夠得知, 這個光源是徹底平行的。

完整代碼以下:

<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.

下章會從繪製一個木塊開始, 最後繪製一個貼圖地球, 此次就是這樣但願與你一塊兒進步。

相關文章
相關標籤/搜索