經過前面幾篇咱們瞭解了座標系、相機、物體等概念, 這一篇咱們要讓3d世界裏的物體, 更像咱們的現實世界的物體, 咱們要爲3d世界繪製光與影。html
若是你看過前兩篇文章, 你會發如今生成物體材質
的時候咱們用的是MeshBasicMaterial
, basic
這單詞意思是基本的
,那也就是說與其相對還會有高級屬性, MeshLambertMaterial
就是高級屬性中的一種。ajax
使用這個屬性建立出來的物體, 會產生暗淡不光亮的表面(你能夠理解爲須要光照時, 它的顏色纔會被看到), 本篇咱們一塊兒看看它的神奇之處。segmentfault
繪製光源
以前, 咱們先搭建一套環境, 這個環境很簡單有物體、牆面、地面, 咱們經過上一篇已經學過如何繪製一個長方體
, 那麼咱們就以薄薄的長方體做爲牆面, 最終效果以下。app
物體、牆面、地面他們身上會有輔助線
, 這個是使用的:dom
const edges = new THREE.BoxHelper(cube, 0x00000); scene.add(edges);
BoxHelper
給立方體設置邊框。cube
須要設置邊框的物體, 後面緊跟着邊框的顏色
。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
替換爲``時如圖:spa
AmbientLight
天然光 or 環境光咱們的第一個主角終於登場了, 下面介紹把光源加入場景的方法。.net
const light = new THREE.AmbientLight('blue'); scene.add(light)
new THREE.AmbientLight('blue')
生成實例時傳入光的顏色, 上面是藍色的光。效果就變成了下面怪異的樣子:3d
地面與牆壁變爲了藍色, 可是在藍色的光照耀下紅色的立方體倒是黑色的。
紅色的物體不能反射藍色的光, 灰色的物體卻能反射藍色的光。
咱們使用紅光的時候:
因此要記住, 一些文章說與天然光顏色不一樣的物體都變爲黑色是錯的!!!
PointLight
點光源 顧名思義他是一個光點
, 有人把它比喻成引火蟲或是小燈泡, 它向四面八方發射光芒, 光源自己
是不可見的因此在咱們繪製的時候會在點光源的位置放置一個立方體表示其位置信息。
const light = new THREE.PointLight('white'); light.intensity = 1.8; light.distance = 30; light.position.set(2, 8, -5); scene.add(light)
點光源的屬性介紹:
intensity
光強, 想要成爲最亮的星。distance
光源照射的距離, 默認值爲0
也就是無限。visible
布爾值, 是否打開光源。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>
想要生成影子可不是那麼簡單的, 由於可想而知在數學方面影子的計算量必然很大的, 在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都要設置上述的屬性。
這個光源是有方向的, 也就是說他能夠指定照向誰, 而且能夠產生陰影。
let light = new THREE.SpotLight("#ffffff"); light.position.set(1, 1, 1); light.target = cube scene.add(light);
可配置的屬性與上面的基本類似, 多了一個target:target
指定照誰
, target
必須是一個THREE.Object3D
對象, 因此咱們常常會先建立一個Object3D
對象, 讓它不可見而後光源就能夠經過照射它, 從而實現任意方向。
咱們先看一下光源在上方照射, 下方物體產生陰影的效果:
開發中咱們會用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>
常常被舉例子的就是太陽光, 實際上太陽光也不是平行的, 只是距離太遠了幾乎能夠算是平行。
這個光源與其餘的不一樣的點是, 他它所照耀的區域接收到的光強是同樣的。
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>
通常狀況下不會只有單一光源, 好比咱們會先放一個環境光, 而後在燈的模型中放上其餘光源, 一些rpg遊戲會用聚光燈
處理用戶視角。
咱們能夠同時使用多個光源, 利用gui.js
查看各類絢麗的效果, 好比咱們能夠用束平型光模擬舞臺效果。
下章會從繪製一個木塊
開始, 最後繪製一個貼圖地球
, 此次就是這樣但願與你一塊兒進步。