[Three.js-03] Three.js 响应式
这是我的学习总结,水平有限,如有错误或不妥的地方还希望大家能够指出来 (^_^)respect! 另外我自己收集的【webgl\three.js\图形学】相关的关资源我已经放在微信群(WX: Galloping_Leo )里面了,需要的话,加群,一起学习共同进步。
intro
先来看一段代码:
import { useEffect, useRef } from "react"
import * as THREE from 'three'
import { AmbientLight } from "three"
import stlyed from 'styled-components'
const Container = styled.div`
height: 100%
& > canvas {
height: 100%;
width:100%
}
`
const useDemo = () => {
const containerRef = useRef<HTMLDivElement>(null)
useEffect(() => {
const container = containerRef.current!
const renderer = new THREE.WebGLRenderer()
container.appendChild(renderer.domElement)
const scene = new THREE.Scene()
const camera = new THREE.PerspectiveCamera(75, 2, 0.1, 100)
camera.position.set(10, 10, 10)
camera.lookAt(0, 0, 0)
const boxGeo = new THREE.BoxGeometry(5, 5, 5)
const mate = new THREE.MeshBasicMaterial({ color: 0xacacac })
const box = new THREE.Mesh(boxGeo, mate)
scene.add(box)
animation()
function animation() {
box.rotateX(0.01)
box.rotateY(0.01)
renderer.render(scene, camera)
requestAnimationFrame(animation)
}
})
return <Container ref={containerRef}></Container>
}
export default useDemo
这段代码的效果如下:
首先正方体边缘非常糊,其次场景中的图像在容器大小改变后被压缩或者拉伸了。
解决糊的问题
在 three.js 中,最后由 canvas 承载渲染后的内容。
canvas 标签本身有 width 和 height 属性,另外通过 css 的 width 和 height 属性也可以设置 canvas 的宽高,但是这两种方式设置宽高有一些区别。
通过 css 来设置宽高,设置的是 canvas 在页面上的大小(css像素大小)。而通过 canvas 本身的属性设置宽高,分为两种情况:
- 当前canvas 标签没有设置 css 宽高时,此时设置 canvas 标签自带的宽高属性,就等于设置 css 属性,控制canvas在页面中的显示大小。
- 当前canvas 标签已经设置了 css 宽高时, 此时再设置 canvas 标签自带的宽高,就是设置 canvas 内部的像素多少。这种情况下,设置的值越大,用于渲染的像素越多,图像也就越细腻。
所以我们可以通过设置 canvas 标签宽高的方式来让场景看起来更细腻。
使用 renderer.setSize(width: number, height: number, updateStyle?: boolean) 方法可以设置 canvas 本身的宽高属性。
// 设置 canva 元素的宽高为父元素的宽高
canvas {
height: 100%;
width: 100%
}
function animation() {
const canvas = renderer.domElement
const pixelRatio = window.devicePixelRatio // 设备像素比 1 css像素对应多少物理设备像素
// 计算出当前设备下的宽高,在不同设备看起来都清晰
const width = (canvas.clientWidth * pixelRatio) | 0
const height = (canvas.clientHeight * pixelRatio) | 0
const needResize = canvas.width !== width || canvas.height !== height
if (needResize) {
// 设置 canvas.width canvas.height 内部像素 (不是css像素)
// updateStyle 设置为 false 不跟新样式
renderer.setSize(width, height, false)
}
// 其他代码省略 ....
}
还有,在创建 renderer 对象时其实有一个参数 antialias (抗锯齿属性) ,把这个值设置为 true 后就开启了抗锯齿特性,物体的边缘就变得清晰了。
const renderer = new THREE.WebGLRenderer({ antialias: true })
效果如下:
解决伸缩问题
场景会被拉伸的原因就是,当 canvas 跟随父容器宽高改变时,相机的宽高比没有发生变化,只需要更新相机的宽高比就可以解决伸缩问题。
注意 更新了相机的属性后,一定要调用 camera.updateProjectionMatrix() 方法,去更新投影矩阵,不然就不会有效果。
function animation() {
// 省略....
if (needResize) {
// 设置 canvas.width canvas.height 内部像素 (不是css像素)
renderer.setSize(width, height, false)
// 设置相机的宽高比
camera.aspect = canvas.clientWidth / canvas.clientHeight
camera.updateProjectionMatrix()
}
// 省略....
}
最后可以封装成一个方法 resizeRendererToDisplaySize
import { WebGLRenderer } from 'three'
export default function resizeRendererToDisplaySize(renderer: WebGLRenderer): boolean {
const canvas = renderer.domElement
const pixelRatio = window.devicePixelRatio
const width = (canvas.clientWidth * pixelRatio) | 0
const height = (canvas.clientHeight * pixelRatio) | 0
const needResize = canvas.width !== width || canvas.height !== height
if (needResize) {
// 设置 canvas.width canvas.height 内部像素 (不是css像素)
renderer.setSize(width, height, false)
}
return needResize
}
使用:
function animation(){
if(resizeRendererToDisplaySize(renderer)) {
const canvas = renderer.domElement
camera.aspet = canvas.clientWidth / canvas.clienHeight
camera.updateProjectionMatrix()
}
// 省略。。。。
}
最后大功告成!既清晰又不会被拉伸。
参考
- responsive
- Learn Three.js Fourth Edition