美文网首页
three.js - Haunted House

three.js - Haunted House

作者: 闪电西兰花 | 来源:发表于2024-01-27 10:53 被阅读0次
    • 基础场景:some lights、no shadow、a Dat.GUI panel
      // 导入three.js
      import * as THREE from 'three'
      // 导入轨道控制器
      import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
      // 导入gui
      import * as dat from 'dat.gui'
    
      /**
       * Scene
      **/
      const scene = new THREE.Scene()
    
      /**
       * Camera
      **/
      const camera = new THREE.PerspectiveCamera(
        75, // 视角
        window.innerWidth / window.innerHeight, // 视椎体长宽比
        0.1, // 近端面
        100 // 远端面
      )
      camera.position.set(4, 2, 5)
    
      /**
       * Renderer
      **/
      const renderer = new THREE.WebGLRenderer()
      renderer.setSize(window.innerWidth, window.innerHeight)
      document.body.appendChild(renderer.domElement)
    
      /**
        * 坐标轴
      **/
      const axesHelper = new THREE.AxesHelper(5) // 坐标轴线段长度
      scene.add(axesHelper)
    
      /**
        控制器(使相机围绕目标运动)
      **/
      const controls = new OrbitControls(camera, renderer.domElement)
      controls.enableDamping = true // 添加轨道阻尼效果
    
      /**
        * 渲染
      **/
      const clock = new THREE.Clock()
      function animate () {
        let elapsedTime = clock.getElapsedTime()
    
        controls.update()
        requestAnimationFrame(animate)
        renderer.render(scene, camera)
      }
      animate()
    
      /**
        * Lights
      **/
      // AmbientLight
      const ambientLight = new THREE.AmbientLight('#fff', 0.8)
      scene.add(ambientLight)
    
      /**
        * gui
      **/
      const gui = new dat.GUI()
      gui.add(ambientLight, 'intensity').name('AmbientLight').min(0).max(1).step(0.001)
    
    • House
      • Floor
      /**
        * House
      **/
      // Floor
      const floor = new THREE.Mesh(
        new THREE.PlaneGeometry(20, 20),
        new THREE.MeshStandardMaterial({
          color: '#a9c388',
      })
      floor.rotation.x = - Math.PI * 0.5
      floor.position.y = 0
      scene.add(floor)
      
      • create a House group 以便于需要整体调整House的位置、大小等
      /**
        * House
      **/
      // Group
      const house = new THREE.Group()
      scene.add(house)
      
      ...
      
      /**
        * floor
      **/
      ...
      
      • create the Walls
      /**
        * House
      **/
      // Group
      const house = new THREE.Group()
      scene.add(house)
      
      // Walls
      const walls = new THREE.Mesh(
        new THREE.BoxGeometry(4, 2.5, 4),
        new THREE.MeshStandardMaterial({
            color: '#ac8e82'
        })
      )
      walls.position.y = 2.5 / 2   // 立方体初始有一半的部分是在坐标轴以下的,也就是在floor的下面
      house.add(walls) // 后续house的部分都添加在house上面
      
      ...
      
      // foor
      ...
      
      • create the Roof, with a pyramid 棱锥体
      // Roof
      const roof = new THREE.Mesh(
        new THREE.ConeGeometry(3.5, 1, 4), 
        new THREE.MeshStandardMaterial({
          color: '#b35f45'
        })
      )
      roof.rotation.y = Math.PI * 0.25
      roof.position.y = 2.5 + 0.5 // walls的高 + 自身高的一半
      house.add(roof)
      
      • creat the Door with a plane
      // Door
      const door = new THREE.Mesh(
        new THREE.PlaneGeometry(2, 2),
        new THREE.MeshStandardMaterial({
          color: '#aa7b7b', 
        })
      )
      door.position.y = 1
      door.position.z = 2 + 0.01 // walls的深度 + 0.01,+0.01避免处在同一层级
      house.add(door)
      
      • add Bushes and use the same geometry and the same material for every bushes
      // Bushes
      const bushGeometry = new THREE.SphereGeometry(1, 16, 16)
      const bushMaterial = new THREE.MeshStandardMaterial({
        color: '#89c854'
      })
      
      const bush1 = new THREE.Mesh(bushGeometry, bushMaterial)
      bush1.scale.set(0.5, 0.5, 0.5)
      bush1.position.set(0.8, 0.2, 2.2)
      
      const bush2 = new THREE.Mesh(bushGeometry, bushMaterial)
      bush2.scale.set(0.25, 0.25, 0.25)
      bush2.position.set(1.4, 0.1, 2.1)
      
      const bush3 = new THREE.Mesh(bushGeometry, bushMaterial)
      bush3.scale.set(0.4, 0.4, 0.4)
      bush3.position.set(-0.8, 0.1, 2.2)
      
      const bush4 = new THREE.Mesh(bushGeometry, bushMaterial)
      bush4.scale.set(0.15, 0.15, 0.15)
      bush4.position.set(-1, 0.05, 2.6)  
      
      house.add(bush1, bush2, bush3, bush4)
      
      • Graves
        1. instead of placing each grave manually, we are going to create and place them procedurally
        2. grave可出现的范围需要被限制在floor的范围内,同时围绕在house的外部出现(环形)
      /**
        * Graves
      **/
      const graves = new THREE.Group()
      scene.add(graves)
      
      const graveGeometry = new THREE.BoxGeometry(0.6, 0.8, 0.2)
      const graveMaterial = new THREE.MeshStandardMaterial({
        color: '#b2b6b1'
      })
      
      for(let i = 0; i < 50; i++) {
        const angle = Math.random() * Math.PI * 2 // 360°
        const radius = 3 + Math.random() * 6 // 随机半径, 3-9
        // 分别设置x z轴的坐标,使grave围绕house画一个圈
        const x = Math.sin(angle) * radius
        const z = Math.cos(angle) * radius
      
        const grave = new THREE.Mesh(graveGeometry, graveMaterial)
        grave.position.set(x, 0.3, z) // y值小于高度的一半,避免z轴出现旋转后底部悬空
        // 旋转随机参数减去0.5,范围-0.5至0.5,确保旋转方向不一致
        grave.rotation.y = (Math.random() - 0.5) * 0.6
        grave.rotation.z = (Math.random() - 0.5) * 0.6
        grave.rotation.x = (Math.random() - 0.5) * 0.6
        graves.add(grave)
      }
      
      • Lights
        1. dim the ambient and moon lights
        2. give those a more blue-ish color 青调
        3. add a warm PointLight above the door and add it to the house
      /*
        * Lights
      */
      // AmbientLight
      const ambientLight = new THREE.AmbientLight('#b9d5ff', 0.12)
      scene.add(ambientLight)
      
      // DirectionalLight
      const moonLight = new THREE.DirectionalLight('#b9d5ff', 0.12)
      moonLight.position.set(4, 5, -2)
      scene.add(moonLight)
      
      // Door Light
      const doorLight = new THREE.PointLight('#ff7d46', 1, 7)
      doorLight.position.set(0, 2.2, 2.7)
      house.add(doorLight)
      
    • 完成House部分后,我们应该会得到如下图所示
      基础结构.png
    • Fog
      • 当前我们看到的floor的边缘部分过于清晰
      • 使用three.js本身就支持的 Fog
      • new THREE.Fog(color, near, far)nearfar分别指的是开始应用雾的最小距离和最大距离,这里的“距离”是指距离camera的远近
      /**
        * Fog
      **/
      const fog = new THREE.Fog('#262837', 1, 15) // color 距离camera的near 距离camera的far
      scene.fog = fog
    
    • Background
      • 继续模糊边缘,使floor和背景融为一体
      • to fix the background, use the same color as Fog
    renderer.setClearColor('#262837')
    
    添加FOG和重置背景色之后的效果.png
    • Textures
      /**
        * Textures
      **/
      const textureLoader = new THREE.TextureLoader()
      
      • door
      const doorColorTexture = textureLoader.load('/imgs/haunted-house/door/color.jpg')
      const doorAlphaTexture = textureLoader.load('/imgs/haunted-house/door/alpha.jpg')
      const doorAmbientOcclusionTexture = textureLoader.load('/imgs/haunted-house/door/ambientOcclusion.jpg')
      const doorHeightTexture = textureLoader.load('/imgs/haunted-house/door/height.jpg')
      const doorNormalTexture = textureLoader.load('/imgs/haunted-house/door/normal.jpg')
      const doorMetalnessTexture = textureLoader.load('/imgs/haunted-house/door/metalness.jpg')
      const doorRoughnessTexture = textureLoader.load('/imgs/haunted-house/door/roughness.jpg')
      
      // Door
      const door = new THREE.Mesh(
        new THREE.PlaneGeometry(2.2, 2.2, 100, 100), // displacementMap会移动顶点实现立体感
        new THREE.MeshStandardMaterial({
          // color: '#aa7b7b', // 添加texture之前作为替代
      
          map: doorColorTexture, // 将简单的纹理作为颜色
      
          transparent: true,
          alphaMap: doorAlphaTexture, // 需要与transparent同时使用
      
          aoMap: doorAmbientOcclusionTexture, // 需要提供uv2坐标支持
      
          displacementMap: doorHeightTexture, // 使door更加立体,不仅仅是一个平面
          displacementScale: 0.1, // 减小移动顶点的高度效应
      
          normalMap: doorNormalTexture, // 法线贴图
      
          metalnessMap: doorMetalnessTexture,
      
          roughnessMap: doorRoughnessTexture
        })
      )
      // support aoMap
      // door.geometry.attributes.uv   自动创建的uv坐标,2个值组成一个坐标值
      door.geometry.setAttribute(
        'uv2', 
        new THREE.Float32BufferAttribute(door.geometry.attributes.uv.array, 2)
      )
      door.position.y = 1
      door.position.z = 2 + 0.01 // walls的深度 + 0.01,+0.01避免处在同一层级
      house.add(door)
      
      • walls
      const bricksColorTexture = textureLoader.load('/imgs/haunted-house/bricks/color.jpg')
      const bricksAmbientTexture = textureLoader.load('/imgs/haunted-house/bricks/ambientOcclusion.jpg')
      const bricksNormalTexture = textureLoader.load('/imgs/haunted-house/bricks/normal.jpg')
      const bricksRoughnessTexture = textureLoader.load('/imgs/haunted-house/bricks/roughness.jpg')
      
      // Walls
      const walls = new THREE.Mesh(
        new THREE.BoxGeometry(4, 2.5, 4),
        new THREE.MeshStandardMaterial({
          map: bricksColorTexture,
      
          aoMap: bricksAmbientTexture, // uv2
      
          normalMap: bricksNormalTexture,
      
          roughnessMap: bricksRoughnessTexture
        })
      )
      // support aoMap
      walls.geometry.setAttribute(
        'uv2', 
        new THREE.Float32BufferAttribute(walls.geometry.attributes.uv.array, 2)
      )
      walls.position.y = 2.5 / 2
      house.add(walls)
      
      • floor
      const grassColorTexture = textureLoader.load('/imgs/haunted-house/grass/color.jpg')
      const grassAmbientTexture = textureLoader.load('/imgs/haunted-house/grass/ambientOcclusion.jpg')
      const grassNormalTexture = textureLoader.load('/imgs/haunted-house/grass/normal.jpg')
      const grassRoughnessTexture = textureLoader.load('/imgs/haunted-house/grass/roughness.jpg')
      
      // 仅添加以上纹理后会发现,grass的大小和house不匹配,继续做如下优化
      
      // repeat
      grassColorTexture.repeat.set(8, 8)
      grassAmbientTexture.repeat.set(8, 8)
      grassNormalTexture.repeat.set(8, 8)
      grassRoughnessTexture.repeat.set(8, 8)
      
      // 避免repeat时拉伸最后一个像素,change the wrapS and wrapT properties
      grassColorTexture.wrapS = THREE.RepeatWrapping
      grassAmbientTexture.wrapS = THREE.RepeatWrapping
      grassNormalTexture.wrapS = THREE.RepeatWrapping
      grassRoughnessTexture.wrapS = THREE.RepeatWrapping
      
      grassColorTexture.wrapT = THREE.RepeatWrapping
      grassAmbientTexture.wrapT = THREE.RepeatWrapping
      grassNormalTexture.wrapT = THREE.RepeatWrapping
      grassRoughnessTexture.wrapT = THREE.RepeatWrapping
      
       /**
       * Floor
      **/
      const floor = new THREE.Mesh(
        new THREE.PlaneGeometry(20, 20),
        new THREE.MeshStandardMaterial({
          map: grassColorTexture,
      
          aoMap: grassAmbientTexture, // uv2
      
          normalMap: grassNormalTexture,
      
          roughnessMap: grassRoughnessTexture
        })
      )
      // support aoMap
      floor.geometry.setAttribute(
        'uv2', 
        new THREE.Float32BufferAttribute(floor.geometry.attributes.uv.array, 2)
      )
      floor.rotation.x = - Math.PI * 0.5
      floor.position.y = 0
      scene.add(floor)
      
    添加textures之后的效果.png
    • Ghosts
      • we are going to represent them with simple lights floating around the house and passing through the ground and graves
      • animate these lights 让ghost以不同的频率围绕house旋转并上下起伏
      • 这里的动画思路可以参考上面的graves的部分
      /*
        * Ghosts
      */
      const ghost1 = new THREE.PointLight('#ff00ff', 2, 3)
      scene.add(ghost1)
    
      const ghost2 = new THREE.PointLight('#00ffff', 2, 3)
      scene.add(ghost2)
    
      const ghost3 = new THREE.PointLight('#ffff00', 2, 3)
      scene.add(ghost3)
    
      /*
        * 渲染
      */
      const clock = new THREE.Clock()
      function animate () {
        let elapsedTime = clock.getElapsedTime()
    
        // Ghosts
        const ghost1Angle = elapsedTime * 0.5
        ghost1.position.x = Math.cos(ghost1Angle) * 4
        ghost1.position.z = Math.sin(ghost1Angle) * 4
        ghost1.position.y = Math.sin(elapsedTime * 3) // 上下起伏
        
        const ghost2Angle = - elapsedTime * 0.32 // 设为负数,与ghost1反向动画
        ghost2.position.x = Math.cos(ghost2Angle) * 5
        ghost2.position.z = Math.sin(ghost2Angle) * 5
        ghost2.position.y = Math.sin(elapsedTime * 4) + Math.sin(elapsedTime * 2.5)
    
        const ghost3Angle = - elapsedTime * 0.18
        ghost3.position.x = Math.cos(ghost3Angle) * (7 + Math.sin(elapsedTime * 0.32)) // 不固定旋转半径
        ghost3.position.z = Math.sin(ghost3Angle) * (7 + Math.sin(elapsedTime * 0.5)) // 不固定旋转半径
        ghost3.position.y = Math.sin(elapsedTime * 4) + Math.sin(elapsedTime * 2.5)
    
    
        controls.update()
        requestAnimationFrame(animate)
        renderer.render(scene, camera)
      }
      animate()
    
    添加ghosts之后会随机出现的PointLight.png
    • Shadows
      • 我们将阴影设置统一放在一起方便管理
      /**
       * Renderer
      **/
      ...
      ...
      
      // Shadows
      ... 以下部分为shadows
      
      
      • activate the shadow map on the renderer
      // 开启场景中的阴影贴图
      renderer.shadowMap.enabled = true
      
      • activate the shadows on the lights that should cast shadows
      moonLight.castShadow = true
      doorLight.castShadow = true
      ghost1.castShadow = true
      ghost2.castShadow = true
      ghost3.castShadow = true
      
      • go through each objects of your scene and decide if that object can cast and/or receive shadows
      walls.castShadow = true
      bush1.castShadow = true
      bush2.castShadow = true
      bush3.castShadow = true
      bush4.castShadow = true
      
      /**
        * Graves
      **/
      for(let i = 0; i < 50; i++) {
        ...
        ...
        grave.castShadow = true
        ...
      }
      
      floor.receiveShadow = true // receive shadow
      
      • optimize the shadow maps
      doorLight.shadow.mapSize.width = 256
      doorLight.shadow.mapSize.height = 256
      doorLight.shadow.camera.far = 7
      
      ghost1.shadow.mapSize.width = 256
      ghost1.shadow.mapSize.height = 256
      ghost1.shadow.camera.far = 7
      
      ghost2.shadow.mapSize.width = 256
      ghost2.shadow.mapSize.height = 256
      ghost2.shadow.camera.far = 7
      
      ghost3.shadow.mapSize.width = 256
      ghost3.shadow.mapSize.height = 256
      ghost3.shadow.camera.far = 7
      
      • 改变阴影映射的算法
      renderer.shadowMap.type = THREE.PCFSoftShadowMap
      
    最终静态效果.png

    相关文章

      网友评论

          本文标题:three.js - Haunted House

          本文链接:https://www.haomeiwen.com/subject/bmtjidtx.html