본문 바로가기
Front-End/ThreeJS

threejs scaling-performance

by 두두리안 2023. 10. 23.
728x90

threejs scaling-perfomance

WebGL을 실행하는 것은 장치의 성능에 따라 상당히 비쌀 수 있다. 이를 완화하려면, 특히 약한 옵션을 포함하여 다양한 장치에서 애플리케이션을 사용할 수 있도록 하려면 성능 최적화를 살펴봐야 한다.

1. 주문형 렌더링

three.js 앱은 일반적으로 초당 60번 실행되는 게임 루프에서 실행된다. 이는 일반적으로 배터리를 가장 많이 소모하고 팬이 회전하게 만드는 원인이다.그러나 장면의 움직이는 부분이 정지되도록 허용되면 렌더링을 계속하는 것은 낭비가 된다 이러한 경우 필요할 때만 렌더링되는 주문형 렌더링을 선택할 수 있다 이렇게 하면 배터리가 절약되고 시끄러운 팬을 억제할 수 있다.

컬러 그레이딩

<Canvas frameloop="demand">
  • demand 옵션은 구성 요소 트리 전체에서 소품 변경이 감지될때마다 프레임을 렌더링 한다.

2. 수동 프레임 트리거

한 가지 주요 주의사항은 트리의 어떤 것이 props를 변경하면 React가 이를 인식할 수 없고 디스플레이가 오래될 것이다. 예를 들어 카메라 컨트롤은 카메라를 잡고 해당 값을 변경합니다. 여기서 React Three Fiber의 invalidate기능을 사용하여 프레임을 수동으로 트리거할 수 있다

function Controls() {
  const orbitControlsRef = useRef()
  const { invalidate, camera, gl } = useThree()
  useEffect(() => {
    orbitControlsRef.current.addEventListener('change', invalidate)
    return () => orbitControlsRef.current.removeEventListener('change', invalidate)
  }, [])
  return <orbitControls ref={orbitControlsRef} args={[camera, gl.domElement]} />
  • React-three/drei 사용되는 controls 에는 이작업을 자동으로 수행한다.

일반적으로 렌더링 해야 할때마다 무효화를 호출할수 있다

invalidate()
  • 호출은 invalidate() 즉시 렌더링되지 않으며 단지 새 프레임이 렌더링되도록 요청
  • 무효화를 여러 번 호출하면 여러 번 렌더링되지 않는다

3. 형상 및 재료 재사용

각 지오메트리와 재질은 GPU에 대한 추가 오버헤드를 의미합니다. 반복될 것이라는 것을 알고 있다면 리소스를 재사용하도록 노력해야 한다.

전역적으로 이 작업을 수행할수 있다

const red = new THREE.MeshLambertMaterial({ color: "red" })
const sphere = new THREE.SphereGeometry(1, 28, 28)

function Scene() {
  return (
    <>
      <mesh geometry={sphere} material={red} />
      <mesh position={[1, 2, 3]} geometry={sphere} material={red} />
	<>
	)
}

4. useLoader를 사용한 캐싱

  • useLoader로 로드된 모든 리소스는 자동으로 캐시된다.

동일한 URL을 사용하여 useLoader를 통해 리소스에 액세스하면 구성 요소 트리 전체에서 항상 동일한 자산을 참조하여 재사용하게 된다. 이는 형상과 재료를 연결하여 재사용 가능한 모델을 생성하기 때문에 GLTFJSX 를 통해 GLTF 자산을 실행하는 경우 특히 유용하다.

GLTF 재사용

function Shoe(props) {
  const { nodes, materials } = useLoader(GLTFLoader, "/shoe.glb")
  return (
    <group {...props} dispose={null}>
      <mesh geometry={nodes.shoe.geometry} material={materials.canvas} />
    </group>
  )
}

<Shoe position={[1, 2, 3]} />
<Shoe position={[4, 5, 6]} />

5. 인스턴스화

각 메시는 드로우 콜이므로, 얼마나 많은 메시를 사용하는지 염두에 두어야 한다. 최대 1000개를 넘지 않으며 최적으로는 수백 개 이하이다. 예를 들어 반복되는 개체를 인스턴스화하여 그리기 호출을 줄여 성능을 다시 얻을 수 있다. 이렇게 하면 단일 그리기 호출에 수십만 개의 개체를 가질 수 있다.

function Instances({ count = 100000, temp = new THREE.Object3D() }) {
  const instancedMeshRef = useRef()
  useEffect(() => {
    // 포지션 설정
    for (let i = 0; i < count; i++) {
      temp.position.set(Math.random(), Math.random(), Math.random())
      temp.updateMatrix()
      instancedMeshRef.current.setMatrixAt(i, temp.matrix)
    }
    // 인스턴스 수정 
    instancedMeshRef.current.instanceMatrix.needsUpdate = true
  }, [])
  return (
    <instancedMesh ref={instancedMeshRef} args={[null, null, count]}>
      <boxGeometry />
      <meshPhongMaterial />
    </instancedMesh>
  )
}

three.js docs

6. 디테일 정도

때로는 물체가 카메라에서 멀어질수록 물체의 품질을 낮추는 것이 유리하다. 거의 보이지 않는데 왜 전체 해상도를 표시할 필요는 없다. 이는 전체 정점 수를 줄이는 좋은 전략이 될 수 있으며 이는 GPU에 대한 작업이 줄어드는 것을 의미한다.

import { Detailed, useGLTF } from '@react-three/drei'

function Model() {
  const [low, mid, high] = useGLTF(["/low.glb", "/mid.glb", "/high.glb"])
  return (
    <Detailed distances={[0, 10, 20]}>
      <mesh geometry={high} />
      <mesh geometry={mid} />
      <mesh geometry={low} />
    <Detailed/>
  )
}

7. 동시성 활성화

React 18에서 동시 스케줄링 및 시간 분할은 중요한 기능으로 도입되었습니다. 이를 통해 React 애플리케이션은 더 나은 성능과 사용자 경험을 제공할 수 있습니다. 다음은 React 18에서 도입된 몇 가지 주요 개념입니다:

  1. 동시 스케줄링 (Concurrent Scheduling): React 18에서는 동시에 여러 작업을 처리하는 더 효율적인 스케줄링 엔진이 도입되었습니다. 이것은 애플리케이션의 업데이트를 더 빠르고 부드럽게 만들어줍니다. 이전 버전의 React에서는 렌더링 작업이 중단되지 않는 한 다른 작업을 수행할 수 없었지만, React 18에서는 작업 간의 우선순위를 관리하여 더 많은 작업을 동시에 처리할 수 있습니다.
  2. startTransition: startTransition 함수는 사용자 경험을 향상시키기 위해 비동기적 업데이트를 지원하는데 사용됩니다. 이 함수를 사용하면 리소스 집중적인 작업이나 렌더링 업데이트가 사용자 인터랙션에 영향을 미치지 않도록 예약할 수 있습니다.
  3. 시간 분할 (Time Slicing): 시간 분할은 애플리케이션의 렌더링 작업을 여러 부분으로 나누어 처리하여 대기 시간을 최소화하는 기술입니다. 이것은 긴 작업을 작은 조각으로 나누고 중요한 업데이트를 먼저 처리하여 사용자 경험을 최적화합니다. 시간 분할은 사용자 인터랙션에 반응하는 동안에도 작업을 계속할 수 있도록 도와줍니다.

React 18은 이러한 기능을 통해 애플리케이션의 성능을 향상시키고 더 매끄러운 사용자 경험을 제공할 수 있도록 설계되었다.

https://github.com/drcmda/scheduler-test

수백 개의 THREE.TextGeometry 인스턴스(정확히는 510개)를 생성하여 과도한 로드를 시뮬레이션합니다. three.js의 다른 많은 클래스와 마찬가지로 이 클래스는 비용이 많이 들고 구성하는 데 시간이 걸립니다. 510개의 인스턴스가 모두 동시에 생성되면 약 1.5초 동안 순수 버벅거림(Apple M1)이 발생하며 일반적으로 탭이 정지됩니다. 이는 일정 간격으로 실행되며 2초마다 실행됩니다 .

React는 동시성을 지원하는 새로운 스케줄러 덕분에 이를 수행할 수 있습니다. 가상 목록이 항목을 예약하는 방법을 생각해 보자. 10 또는 10.000.000을 지정하더라도 화면이 차지할 수 있는 만큼만 렌더링된다. 하지만 React는 이를 기본 수준에서 수행하며 모든 작업에 가중치를 부여한다. 작업이 프레임 속도에 영향을 미치기 시작하면 React는 균형을 맞춰야 하기 때문이다.

작동방식

https://www.youtube.com/watch?v=nLF0n9SACd4

 

728x90

'Front-End > ThreeJS' 카테고리의 다른 글

Three.js 렌더링 성능 최적화: 웹 3D 그래픽의 성능을 향상시키는 전략  (1) 2023.11.02
Geometries  (0) 2023.07.25
Object3D Hierarchy  (0) 2023.07.24
Dat GUI  (0) 2023.07.21
ThreeJS 시작하기  (0) 2023.07.20