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

接下来就是实现跳一跳中的小人了

相关文章
相关标签/搜索