밝을희 클태

react-three/drei Environment 태그 렌더링 블로킹 본문

KEYNUT 프로젝트

react-three/drei Environment 태그 렌더링 블로킹

huipark 2024. 11. 29. 14:49

Blender로 만든 3D 모델을 GLB 파일로 export하여 프로젝트의 메인 페이지에 넣으려고 했다.

 

그러나 GLB 파일의 크기가 큰 편이라, 네트워크가 조금이라도 느린 상황에서는 빈 화면이 오래 노출될 수 있었다. 이를 방지하기 위해, GLB 파일 다운로드 상태를 실시간으로 표시하는 기능을 구현했다. 진행 상황을 1%부터 100%까지 표현하려고 했다.

전체 코드

'use client';
import React, { useState, useEffect } from 'react';
import { Canvas } from '@react-three/fiber';
import { Environment, OrbitControls, useGLTF } from '@react-three/drei';
function Model({ url }: { url: string }) {
const { scene } = useGLTF(url);
return <primitive object={scene} />;
}
export default function ModelViewer() {
const [modelUrl, setModelUrl] = useState<string | null>(null);
const [progress, setProgress] = useState<number>(0);
useEffect(() => {
const downloadModel = async () => {
try {
const response = await fetch(`${process.env.NEXT_PUBLIC_IMAGE_DOMAIN}/case_cherry.glb`);
const reader = response.body?.getReader();
const contentLength = response.headers.get('Content-Length');
const total = contentLength ? Number(contentLength) : null;
if (!reader || !total) {
console.error('ReadableStream not supported or Content-Length unavailable.');
return;
}
let received = 0;
const chunks: Uint8Array[] = [];
while (true) {
const { done, value } = await reader.read();
if (done) break;
if (value) {
chunks.push(value);
received += value.length;
setProgress(Math.round((received / total) * 100));
}
}
const blob = new Blob(chunks, { type: 'application/octet-stream' });
const blobUrl = URL.createObjectURL(blob);
setModelUrl(blobUrl);
} catch (error) {
console.error('Failed to download model:', error);
}
};
downloadModel();
}, []);
return (
<div className="w-full h-full relative flex justify-center items-center">
<Canvas camera={{ position: [-40, 25, 50], fov: 7 }} shadows>
<Environment files="/textures/shanghai_bund_1k.hdr" />
{modelUrl && <Model url={modelUrl} />}
{modelUrl && <OrbitControls autoRotate enableZoom={false} />}
</Canvas>
<div className={`absolute transition-opacity ${modelUrl ? 'opacity-0' : 'opacity-100'}`}>
<progress value={progress} max="100" aria-valuenow={progress} aria-valuemin={0} aria-valuemax={100}></progress>
</div>
</div>
);
}

문제

GLB 다운로드 진행 상황이 1%부터 매끄럽게 표시되지 않고, 중간부터 진행 상황이 표시되는 문제가 발생했다.

원인

<Environment> 태그에서 HDR 파일을 다운로드하는 동안 UI 리렌더링이 블로킹되는 것이 원인이었다.

React의 상태(setProgress)가 업데이트되어도, Environment의 비동기 작업이 진행 중일 때 UI가 즉시 반영되지 않았다.

해결

React의 <Suspense> 태그로 <Environment>를 감싸주어 해결했다.

<Suspense>
<Environment files="/textures/shanghai_bund_1k.hdr" />
</Suspense>

Suspense가 해결한 이유 (예측)

<Environment> 태그가 HDR 파일을 다운로드하는 동안, 는 React에게 해당 작업이 비동기적임을 알려서 UI 업데이트 블로킹을 해결하는 거 같다?.