25.Three.js 解决方案之添加背景和天空盒
25 Three.js 解决方案之添加背景和天空盒
在我们之前所有演示的案例中,场景中的背景往往使用默认的黑色,或者是其他纯颜色。
下面我们讲解一下 Three.Screen 的背景设置方式。
设置场景背景
场景(Three.Screen)有一个属性 .background。我们可以通过设置这个属性来给场景添加背景。
.background 属性值类型
场景背景属性值一共有 3 种类型:
-
默认为 null
.background 属性值为 null,场景显示为黑色
-
某颜色 Three.Color
Three.Color 可接受 字符串或数字类型的颜色值,例如:
- new Three.Color(’#333')
- new Three.Color(‘green’)
- new Three.Color(0x333333)
-
某纹理 Three.Texture
设置背景色示例代码:
const scene = new Three.Scene()
scene.background = new Three.Color(0x333333)
设置背景纹理图片示例代码:
const scene = new Three.Scene()
const textureLoader = new Three.TextureLoader()
scene.background = textureLoader.load(require('@/assets/imgs/blue_sky.jpg').default)
或者是
const textureLoader = new Three.TextureLoader()
textureLoader.load(require('@/assets/imgs/blue_sky.jpg').default, (texture) => {
scene.background = texture
})
上面设置场景纹理背景图,实际运行后你会发现虽然背景图显示了,但是背景图却有可能是变形着的。
这是由于背景图片本身就一个宽高比,而画布(Canvas)本身也有一个宽高比。
实际上是渲染器渲染尺寸的宽高,例如每次浏览器窗口尺寸发生变化时,我们都会重新设置 渲染尺寸
renderer.setSize(width, height, false)
判断高宽比,不让背景图变形且可以铺满整个背景
假设我们不能接受背景图变形,那么我们就需要计算一下 2 者的宽高比,然后找出合适的比例进行修改。
这个不让背景图变形的计算过程是:
- 计算出画布宽高比,例如 canvasAspect
- 计算出背景图宽高比,例如 imgAspect
- 然后计算 imgAspect/canvasAspect,得到 最终背景图在不变形的前提下的缩放比,例如 const resultAspect = imgAspect / canvasAspect
- 然后依次设置背景图纹理的偏移(offset.x、offset.y),以及判断是否需要重复平铺背景图(repeat.x、repeat.y)
示例代码如下:
const textureRef = useRef<Three.Texture | null>(null)
...
const textureLoader = new Three.TextureLoader()
textureLoader.load(require('@/assets/imgs/blue_sky.jpg').default, (texture) => {
textureRef.current = texture
scene.background = textureRef.current
handleResize() //此处是当纹理图片加载完成后,需要调用执行一下 handleResize()
})
...
const handleResize = () => {
const canvasAspect = width / height //第1步:计算出画布宽高比
if (textureRef.current !== null) {
const bgTexture = textureRef.current
const imgAspect = bgTexture.image.width / bgTexture.image.height //第2步:计算出背景图宽高比
const resultAspect = imgAspect / canvasAspect //第3步:计算出最终背景图宽缩放宽高比
//第4步:设置背景图纹理的偏移和重复
bgTexture.offset.x = resultAspect > 1 ? (1 - 1 / resultAspect) / 2 : 0
bgTexture.repeat.x = resultAspect > 1 ? 1 / resultAspect : 1
bgTexture.offset.y = resultAspect > 1 ? 0 : (1 - resultAspect) / 2
bgTexture.repeat.y = resultAspect > 1 ? 1 : resultAspect
}
}
完整的示例代码如下:
import { useRef, useEffect } from "react"
import * as Three from 'three'
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls"
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader"
import './index.scss'
const HelloSkybox = () => {
const canvasRef = useRef<HTMLCanvasElement | null>(null)
const textureRef = useRef<Three.Texture | null>(null)
useEffect(() => {
if (canvasRef.current === null) { return }
const renderer = new Three.WebGLRenderer({ canvas: canvasRef.current })
const scene = new Three.Scene()
const textureLoader = new Three.TextureLoader()
textureLoader.load(require('@/assets/imgs/blue_sky.jpg').default, (texture) => {
textureRef.current = texture
scene.background = textureRef.current
handleResize()
})
scene.background = textureRef.current
const camera = new Three.PerspectiveCamera(45, 2, 0.1, 100)
camera.position.set(10, 0, 10)
const light = new Three.HemisphereLight(0xFFFFFF, 0x333333, 1)
scene.add(light)
const loader = new GLTFLoader()
loader.load(require('@/assets/model/hello.glb').default, (gltf) => {
scene.add(gltf.scene)
})
const control = new OrbitControls(camera, canvasRef.current)
control.update()
const render = () => {
renderer.render(scene, camera)
window.requestAnimationFrame(render)
}
window.requestAnimationFrame(render)
const handleResize = () => {
if (canvasRef.current === null) { return }
const width = canvasRef.current.clientWidth
const height = canvasRef.current.clientHeight
const canvasAspect = width / height
if (textureRef.current !== null) {
const bgTexture = textureRef.current
const imgAspect = bgTexture.image.width / bgTexture.image.height
const resultAspect = imgAspect / canvasAspect
bgTexture.offset.x = resultAspect > 1 ? (1 - 1 / resultAspect) / 2 : 0
bgTexture.repeat.x = resultAspect > 1 ? 1 / resultAspect : 1
bgTexture.offset.y = resultAspect > 1 ? 0 : (1 - resultAspect) / 2
bgTexture.repeat.y = resultAspect > 1 ? 1 : resultAspect
}
camera.aspect = canvasAspect
camera.updateProjectionMatrix()
renderer.setSize(width, height, false)
}
handleResize()
window.addEventListener('resize', handleResize)
return () => {
window.removeEventListener('resize', handleResize)
}
}, [])
return (
<canvas ref={canvasRef} className='full-screen' />
)
}
export default HelloSkybox
请注意,上面讲述的是将背景图片加载进 Three.js 中,并当做纹理来使用。我们可以通过修改纹理各种属性来修改和控制背景图。
上面示例代码中仅仅是对纹理的偏移和重复进行了设置
但是,假设就仅仅为了达到上述效果,实际上我们根本不用搞这么复杂,直接给网页中 <canvas> 标签设置一个背景图片即可。
第 1 步:添加渲染器参数 alpha:true
const renderer = new Three.WebGLRenderer({ canvas: canvasRef.current, alpha: true })
**第2步:给画布标签(