初始场景
一个平面,一个球体用来测试环境光和月光,测得光源正常后移除球体
创建房屋组别
因为我们房屋可能包含许多东西像是门、屋顶之类的,所以需要创建一个房屋组,将所有属于屋子的物体给添加到这个组里面,之后当我们需要移动整个屋子时,变可以把房屋组当作一个整体去进行位置移动,而不用单个单个的去移动物体位置。
// 创建一个house组
const house = new THREE.Group()
scene.add(house)
下面先初始化房屋该有的物体,具体细节后面再设置
创建墙体
//墙体
const walls = new THREE.Mesh(
new THREE.BoxBufferGeometry(4, 2.5, 4),
new THREE.MeshStandardMaterial({ color: '#ac8e82' })
)
walls.position.y = 2.5 / 2
// 添加到房屋组别中
house.add(walls)
创建屋顶
可以使用圆锥缓冲几何体来创建屋顶
//屋顶
const roof = new THREE.Mesh(
new THREE.ConeBufferGeometry(3.5, 1, 4),
new THREE.MeshStandardMaterial({ color: '#b35f45' })
)
// 旋转45度以适配立方体
roof.rotation.y = Math.PI * 0.25
roof.position.y = 2.5 + 1 / 2
house.add(roof)
创建门
//门
const door = new THREE.Mesh(
new THREE.PlaneBufferGeometry(2,2),
new THREE.MeshStandardMaterial({color:'#aa7b7b'})
)
door.position.z = 2 + 0.01
door.position.y = 2/2
house.add(door)
创建灌木丛
//草丛
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.1)
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)
创建墓碑组别
营造鬼屋氛围,在屋子旁边放一些墓碑
/**
* 坟墓组
*/
const graves = new THREE.Group()
scene.add(graves)
创建墓碑
我们要创建几十个墓碑,不可能一个个去加,因此使用for循环来做。
因为我们的地面中心处于原点,房屋也位于地面中心,因此各个墓碑的位置应该也是围绕着屋子以原点为中心四散分布,也就是墓碑放置轨迹应该是以原点为圆心的一个圆。
下图为例,橙色即为我们的屋子,紫叉即是各个墓碑
const graveGeometry = new THREE.BoxBufferGeometry(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 //角度
const radius = 3 + Math.random() * 6 //半径位于房屋和地面边缘之间
const x = Math.cos(angle) * radius
const z = Math.sin(angle) * radius
const grave = new THREE.Mesh(graveGeometry, graveMaterial)
grave.position.set(x, 0.3, z)
//改变墓碑朝向以及倾斜度
grave.rotation.y = (Math.random() - 0.5)*0.5
grave.rotation.z = (Math.random() - 0.5)*0.5
graves.add(grave)
}
灯光
营造鬼屋氛围,先把环境光和月光颜色调蓝一点,光强弱一些
const ambientLight = new THREE.AmbientLight('#b9d5ff', 0.12)
const moonLight = new THREE.DirectionalLight('b9d5fff', 0.12)
创建门灯
在门顶部添加点光源
//门灯
const doorLight = new THREE.PointLight('#ff7d46',1,7)
doorLight.position.set(0,2.2,2.7)
// 添加到房屋组
house.add(doorLight)
烟雾
往场景添加烟雾
//Fog
//创建烟雾
const fog = new THREE.Fog('#262837',1,15)
//设置场景的fog属性
scene.fog = fog
Fog( color : Integer, near : Float, far : Float )
.color
: Color
雾的颜色。比如说,如果将其设置为黑色,远处的物体将被渲染成黑色。
.near
: Float
开始应用雾的最小距离。距离小于活动摄像机“near”个单位的物体将不会被雾所影响。默认值是1。
.far
: Float
结束计算、应用雾的最大距离,距离大于活动摄像机“far”个单位的物体将不会被雾所影响。默认值是1000。
我们可以把渲染器背景颜色设为与烟雾颜色一样,这样场景边缘看起来就不会太突兀
renderer.setClearColor('#262837')
设置纹理
// 创建纹理加载器
const textureLoader = new THREE.TextureLoader()
门
加载所有关于门的纹理
const doorColorTexture = textureLoader.load('/textures/door/color.jpg')
const doorAlphaTexture = textureLoader.load('/textures/door/alpha.jpg')
const doorHeightTexture = textureLoader.load('/textures/door/height.jpg')
const doorAmbientOcclusionTexture = textureLoader.load(
'/textures/door/ambientOcclusion.jpg'
)
const doorNormalTexture = textureLoader.load('/textures/door/normal.jpg')
const doorMetalnessTexture = textureLoader.load('/textures/door/metalness.jpg')
const doorRoughnessTexture = textureLoader.load('/textures/door/roughness.jpg')
设置门的材质
//门
const door = new THREE.Mesh(
new THREE.PlaneBufferGeometry(2.2, 2.2,100,100),
new THREE.MeshStandardMaterial({
map: doorColorTexture,
//设置alphaMap记得开启透明度属性
transparent: true,
alphaMap: doorAlphaTexture,
aoMap: doorAmbientOcclusionTexture,
//设置displacementMap记得提供更多顶点给几何体
displacementMap: doorHeightTexture,
displacementScale: 0.1,
normalMap: doorNormalTexture,
metalnessMap: doorMetalnessTexture,
roughnessMap: doorRoughnessTexture,
})
)
//设置aoMap记得设置uv2属性
door.geometry.setAttribute(
'uv2',
new THREE.Float32BufferAttribute(door.geometry.attributes.uv.array, 2)
)
墙壁砖块
加载墙面砖块纹理
const brickColorTexture = textureLoader.load('/textures/bricks/color.jpg')
const brickAmbientOcclusionTexture = textureLoader.load(
'/textures/bricks/ambientOcclusion.jpg'
)
const brickNormalTexture = textureLoader.load('/textures/bricks/normal.jpg')
const brickRoughnessTexture = textureLoader.load(
'/textures/bricks/roughness.jpg'
)
设置墙体材质
//墙体
const walls = new THREE.Mesh(
new THREE.BoxBufferGeometry(4, 2.5, 4),
new THREE.MeshStandardMaterial({
map: brickColorTexture,
aoMap: brickAmbientOcclusionTexture,
normalMap: brickNormalTexture,
roughnessMap: brickRoughnessTexture,
})
)
walls.geometry.setAttribute(
'uv2',
new THREE.Float32BufferAttribute(walls.geometry.attributes.uv.array, 2)
)
地面草坪
加载草坪纹理
const grassColorTexture = textureLoader.load('/textures/grass/color.jpg')
const grassAmbientOcclusionTexture = textureLoader.load(
'/textures/grass/ambientOcclusion.jpg'
)
const grassNormalTexture = textureLoader.load('/textures/grass/normal.jpg')
const grassRoughnessTexture = textureLoader.load(
'/textures/grass/roughness.jpg'
)
设置地面材质
// Floor
const floor = new THREE.Mesh(
new THREE.PlaneBufferGeometry(20, 20),
new THREE.MeshStandardMaterial({
map: grassColorTexture,
aoMap: grassAmbientOcclusionTexture,
normalMap: grassNormalTexture,
roughnessMap: grassRoughnessTexture,
})
)
floor.geometry.setAttribute(
'uv2',
new THREE.Float32BufferAttribute(floor.geometry.attributes.uv.array, 2)
)
设置完后观察上图你会发现草坪非常违和,这是因为我们把一张草坪纹理图片给应用到整块平面上,因此我们要设置纹理repeat
属性使得纹理在一块平面上多次重复
grassColorTexture.repeat.set(8,8)
grassAmbientOcclusionTexture.repeat.set(8,8)
grassNormalTexture.repeat.set(8,8)
grassRoughnessTexture.repeat.set(8,8)
grassColorTexture.wrapS = THREE.RepeatWrapping
grassAmbientOcclusionTexture.wrapS = THREE.RepeatWrapping
grassNormalTexture.wrapS = THREE.RepeatWrapping
grassRoughnessTexture.wrapS = THREE.RepeatWrapping
grassColorTexture.wrapT = THREE.RepeatWrapping
grassAmbientOcclusionTexture.wrapT = THREE.RepeatWrapping
grassNormalTexture.wrapT = THREE.RepeatWrapping
grassRoughnessTexture.wrapT = THREE.RepeatWrapping
现在和谐多了
增加幽灵漂浮效果
我们用一些简单的灯光来制造出幽灵漂浮的效果,这些灯光会在房子四周漂浮并且会穿过草坪和墓碑
添加点光源
//ghost
const ghost1 = new THREE.PointLight('#ff00ff',2,3)
const ghost2 = new THREE.PointLight('#00ffff',2,3)
const ghost3 = new THREE.PointLight('#ffff00',2,3)
scene.add(ghost1,ghost2, ghost3)
设置动画
/**
* Animate
*/
const clock = new THREE.Clock()
const tick = () => {
const elapsedTime = clock.getElapsedTime()
// Update controls
controls.update()
//update ghost
const ghostAngle = elapsedTime * 0.5
//x和z设置点光源圆周运动
ghost1.position.x = Math.cos(ghostAngle) * 4
ghost1.position.z = Math.sin(ghostAngle) * 4
//设置点光源高度上下变化
ghost1.position.y = Math.sin(ghostAngle * 3)
const ghost2Angle = -elapsedTime * 0.32
ghost2.position.x = Math.cos(ghost2Angle) * 5
ghost2.position.z = Math.sin(ghost2Angle) * 4
ghost2.position.y = Math.sin(ghost2Angle * 3) + Math.sin(elapsedTime * 2.5)
const ghost3Angle = -elapsedTime * 0.27
//以不同半径旋转
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(ghost3Angle * 3) + Math.sin(elapsedTime * 2.5)
// Render
renderer.render(scene, camera)
// Call tick again on the next frame
window.requestAnimationFrame(tick)
}
阴影
激活阴影
renderer.shadowMap.enabled = true
为能投射阴影的光源开启castShadow
moonLight.castShadow = true
doorLight.castShadow = true
ghost1.castShadow = true
ghost2.castShadow = true
ghost2.castShadow = true
为物体开启castShadow
walls.castShadow = true
bush1.castShadow = true
bush2.castShadow = true
bush3.castShadow = true
bush4.castShadow = true
for (let i = 0; i < 50; i++) {
......
grave.castShadow = true
......
}
出于性能考虑,需要优化阴影贴图
一般来讲,优化步骤是通过设置相机助手,把用于渲染阴影的灯光的摄像机给添加到助手里面,再调整相机的远近等属性,缩小灯光相机可视范围。可以参考另外一篇笔记Shadows阴影
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
最后改变阴影贴图的算法类型
// PCF柔软阴影贴图
renderer.shadowMap.type = THREE.PCFSoftShadowMap