Three.js 學習筆記 - 給跳一跳小遊戲添加光源,陰影

一. 修改物體材質

接着上一篇的項目,在上一篇中物體的材質都是用的MeshBasicMaterial這種材質,這種材質是不受光照的影響的,因此要修改爲MeshPhongMaterial這種材質,讓它受光照的影響。html

在cylinder.js 和 box.js中將MeshBasicMaterial改成MeshPhongMaterialgit

const material = new THREE.MeshPhongMaterial({
    color: 0xffffff
})
複製代碼

這時候就渲染結果就是這個樣子:github

物體都看不見了,由於沒有光面試

二. 添加環境光

官方文檔是這樣解釋環境光的:canvas

環境光會均勻的照亮場景中的全部物體。環境光不能用來投射陰影,由於它沒有方向。api

能夠理解爲從各個方向照向物體的光bash

在renderer目錄下,新建light.js工具

const light = () => {
  return {
    ambientLight: new THREE.AmbientLight(0xffffff, 0.8)
  }
}
export default light
複製代碼

new THREE.AmbientLight第一個參數是光的顏色,第二個參數是光強。測試

scene.jsui

import light from './light'

const scene = () => {
  const sceneInstance = new THREE.Scene()
  const axesHelper = new THREE.AxesHelper(100)
  const lights = light()
  Object.keys(lights).forEach(value => {
    sceneInstance.add(lights[value])
  })
  sceneInstance.add(axesHelper)
  return sceneInstance
}

export default scene
複製代碼

效果:

如今物體就能夠看見了,因爲環境光不能投射陰影,因此須要其餘的光,下面試下其餘光的效果

三. 其餘類型的光

  • 平行光

    平行光就是從光源處到物體的光線都是平行的,官方文檔上說相似太陽光,它是能夠產生陰影的。

    light.js

    const light = () => {
        const directionalLight = new THREE.DirectionalLight(0xFF8C00)
        directionalLight.position.set(10, 30, 20) // 設置光源的位置
        return {
            ambientLight: new THREE.AmbientLight(0xffffff, 0.5),
            directionalLight
        }
    }
    export default light
    複製代碼

    看下效果:

    由於不加環境光太暗了,因此仍是在有環境光的狀況下測試,這時候就能看出來物體不一樣的面受到的光照強度是不一樣的。

  • 半球光

    並無從文檔上看懂半球光是什麼。。先記住一點半球光不能產生陰影,把以前的平行光改爲半球光:

    light.js

    const light = () => {
        const hemisphereLight = new THREE.HemisphereLight(0x8A2BE2, 0xFFFAFA, 1)
        hemisphereLight.position.set(10, 30, 20) // 設置光源的位置
        return {
            ambientLight: new THREE.AmbientLight(0xffffff, 0.5),
            hemisphereLight
        }
    }
    export default light
    複製代碼

    效果:

    這篇文章給了詳細的例子,能夠看看。地址

  • 點光源

    這個好理解,就像蠟燭或者燈泡發出的光線。

    light.js

    const light = () => {
        const pointLight = new THREE.PointLight(0x1a94bc, 1, 100)
        pointLight.position.set(10, 30, 20) // 設置光源的位置
        return {
            ambientLight: new THREE.AmbientLight(0xffffff, 0.5),
            pointLight
        }
    }
    export default light
    複製代碼

    看起來和平行光也很像

    可是這個點光源是有一個衰減值的,就是光源離物體越遠,光的強度就越低,平行光不會,距離再遠也是相同的光強

  • 平面光光源

    這個從名字上就能理解,就是從一個平面發出平行光。

    light.js

    const light = () => {
        const pointLight = new THREE.PointLight(0x1a94bc, 1, 100, 100)
        pointLight.position.set(10, 30, 20) // 設置光源的位置
        return {
            ambientLight: new THREE.AmbientLight(0xffffff, 0.5),
            pointLight
        }
    }
    export default light
    複製代碼

    這種光只支持 MeshStandardMaterial 和 MeshPhysicalMaterial 兩種材質,因此還得去改一下box.js 和 cylinder.js 中物體的材質

    const material = new THREE.MeshStandardMaterial({
      color: 0xffffff
    })
    複製代碼

    和平行光的效果差很少

  • 聚光燈

    這個在官方文檔上有個很好的例子,一看就明白了,這裏就不寫了。

    看了效果以後決定選擇平行光,由於能產生陰影,效果也還能夠,最終light.js的代碼:

    const light = () => {
        const directionalLight = new THREE.DirectionalLight(0xffffff, 0.3)
        directionalLight.position.set(10, 30, 20) // 設置光源的位置
        return {
            ambientLight: new THREE.AmbientLight(0xffffff, 0.8),
            directionalLight
        }
    }
    export default light
    複製代碼

    記得把box.js 和 cylinder.js 中的材質改回來(MeshPhongMaterial)

三. 實現陰影效果

實現陰影效果須要具有如下條件:

  1. 渲染器啓用陰影
  2. 能夠造成陰影的光源
  3. 可以表現陰影的材質
  4. 物體能夠投射陰影
  5. 接受陰影的物體

如今就一步一步實現上面的條件:

  • 渲染器開啓陰影

    renderer -> index.js 添加

    init () {
        this.instance.shadowMap.enabled = true   // 新增,渲染器啓用陰影
    }
    複製代碼
  • 造成陰影的光源

    上面說過平行光是能夠投射陰影的,因此用它就行了。

    ligth.js

    const light = () => {
        const directionalLight = new THREE.DirectionalLight(0xffffff, 0.3)
        directionalLight.position.set(10, 30, 20) // 設置光源的位置
        directionalLight.castShadow = true  // 新增,產生動態陰影
        
        directionalLight.shadow.camera.near = 0.5 // 新增
        directionalLight.shadow.camera.far = 500 // 新增
        directionalLight.shadow.camera.left = -100 // 新增
        directionalLight.shadow.camera.right = 100 // 新增
        directionalLight.shadow.camera.top = 100 // 新增
        directionalLight.shadow.camera.bottom = -100 // 新增
        
        directionalLight.shadow.mapSize.width = 1024 // 新增
        directionalLight.shadow.mapSize.height = 1024 // 新增
        return {
            ambientLight: new THREE.AmbientLight(0xffffff, 0.8),
            directionalLight
        }
    }
    export default light
    複製代碼
  • 可以表現陰影的材質

    物體使用的是MeshPhongMaterial材質,這個材質是能夠產生陰影的,這裏就不用改了

  • 物體能夠投射陰影

    在cylinder.js 和 box.js 中分別添加以下代碼:

    create () {
        this.instance.castShadow = true // 新增,讓物體能夠投射陰影
        this.instance.receiveShadow = true // 新增,讓物體能夠接受陰影
    }
    複製代碼
  • 接受陰影的物體

    在現實生活中接受陰影的物體最多見的就是地面了,這裏也是同樣,須要給如今的場景添加一個地面

    新建ground.js

    const ground = () => {
        const groundGeometry = new THREE.PlaneGeometry(200, 200)
        const groundMaterial = new THREE.MeshStandardMaterial({
            color: 0xffffff
        })
        const instance = new THREE.Mesh(groundGeometry, groundMaterial)
        instance.receiveShadow = true
    
        return instance
    }
    
    export default ground
    複製代碼

    如今去看看效果:

    這個效果炸了呀。

    ground變成了牆而不是地面。因此須要旋轉一下

    讓他沿着x軸轉負90度

    instance.rotateX(-Math.PI / 2)
    複製代碼

    效果:

    如今就和x軸在同一平面了,只是有點高,把物體切斷了,再往下移一點

    instance.position.y = -5
    複製代碼

    如今的效果是:

  • 添加背景

    因爲這樣的地面太醜,如今不用MeshStandardMaterial這個材質了,改用ShadowMaterial材質,文檔

    ground.js

    const ground = () => {
        const groundGeometry = new THREE.PlaneGeometry(200, 200)
        const groundMaterial = new THREE.ShadowMaterial({
            color: 0x000000,
            opacity: 0.3
        })
        const instance = new THREE.Mesh(groundGeometry, groundMaterial)
        instance.rotateX(-Math.PI / 2)
        instance.position.y = -5
        instance.receiveShadow = true
    
        return instance
    }
    
    export default ground
    
    複製代碼

    改完以後如今整個地面又變黑了,暫時不用管,加上背景就行了

    背景應該和相機看到的範圍同樣大,因此背景的尺寸要用到相機相關的變量size,這裏爲了方便維護,把這個變量單獨存放:

    camera_config.js

    export default {
        size: 30
    }
    複製代碼

    新建background.js

    import cameraConfig from '../../configs/camera_config'
    
    const background = () => {
        const backgroundGeometry = new THREE.PlaneGeometry(
            cameraConfig.size * 2,
            window.innerHeight / window.innerWidth * cameraConfig.size * 2
        )
        const backgroundMaterial = new THREE.MeshBasicMaterial({
            color: 0xd7dbe6
        })
        const instance = new THREE.Mesh(backgroundGeometry, backgroundMaterial)
        instance.position.z = -85
    
        return instance
    }
    
    export default background
    
    複製代碼

    camera.js修改成下面這樣

    import cameraConfig from '../../configs/camera_config'  // 新增
    
    const camera = () => {
        const ratio = window.innerHeight / window.innerWidth
        const { size } = cameraConfig  // 改動
        // 設置相機可看到範圍
        const cameraInstance = new THREE.OrthographicCamera(
            -size, size, size * ratio, -size * ratio, -100, 85  // 改動
        )
    
        cameraInstance.position.set(-10, 10, 10) // 設置相機位置
        cameraInstance.up.set(0, 1, 0)
        cameraInstance.lookAt(new THREE.Vector3(0, 0, 0)) // 設置相機位置從0, 0, 0望向0, 0, 0
    
        return cameraInstance
    }
    
    export default camera
    
    複製代碼

    renderer -> index.js

    import camera from './camera'
    import scene from './scene'
    import background from '../objects/background'  // 新增
    
    class Renderer {
        constructor () {
            this.camera = null
            this.scene = null
        }
        init () {
            this.camera = camera()
            this.scene = scene()
            const { width, height } = canvas
            if (window.devicePixelRatio) {
                canvas.height = height * window.devicePixelRatio
                canvas.width = width * window.devicePixelRatio
            }
            this.instance = new THREE.WebGLRenderer({
                canvas,
                antialias: true
            })
            this.instance.shadowMap.enabled = true
            this.scene.add(this.camera)  // 新增,這一步很重要
            this.camera.add(background())  // 新增
        }
    
        render () {
            this.instance.render(this.scene, this.camera)
        }
    }
    
    export default new Renderer()
    
    複製代碼

    去開發者工具看看效果:

    如今就比較正常了。

    完整代碼GitHub

接下來就是實現跳一跳中的小人了

相關文章
相關標籤/搜索