[Three.js-03] Three.js 响应式

734 阅读3分钟

[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

这段代码的效果如下:

响应式-2.gif

首先正方体边缘非常糊,其次场景中的图像在容器大小改变后被压缩或者拉伸了。

解决糊的问题

在 three.js 中,最后由 canvas 承载渲染后的内容。

canvas 标签本身有 width 和 height 属性,另外通过 css 的 width 和 height 属性也可以设置 canvas 的宽高,但是这两种方式设置宽高有一些区别。

通过 css 来设置宽高,设置的是 canvas 在页面上的大小(css像素大小)。而通过 canvas 本身的属性设置宽高,分为两种情况:

  1. 当前canvas 标签没有设置 css 宽高时,此时设置 canvas 标签自带的宽高属性,就等于设置 css 属性,控制canvas在页面中的显示大小。
  2. 当前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 })

效果如下:

响应式-3.gif

这时候立方体终于清晰了,但是当改变父容器大小时,立方体任然会被拉伸或者压缩。。。

解决伸缩问题

场景会被拉伸的原因就是,当 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()
  }
  
  // 省略。。。。
}

最后大功告成!既清晰又不会被拉伸。

响应式-4.gif

参考

  1. responsive
  2. Learn Three.js Fourth Edition