perf: AntiquityViewer render theo nhu cau + gate ngoai man hinh + pixelRatio 1.5 + meshopt

This commit is contained in:
tuyenio
2026-06-25 08:51:40 +07:00
parent e0117ab4bd
commit c96b6f195c
@@ -5,6 +5,8 @@ import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js"; import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js"; import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
import { RoomEnvironment } from "three/examples/jsm/environments/RoomEnvironment.js"; import { RoomEnvironment } from "three/examples/jsm/environments/RoomEnvironment.js";
import { MeshoptDecoder } from "three/examples/jsm/libs/meshopt_decoder.module.js";
import { createRenderGate } from "@/app/lib/render-gate";
import { Rotate3d, Pause, Play, Maximize2 } from "lucide-react"; import { Rotate3d, Pause, Play, Maximize2 } from "lucide-react";
type Status = "loading" | "ready" | "error"; type Status = "loading" | "ready" | "error";
@@ -35,7 +37,7 @@ export function AntiquityViewer({ src }: { src: string }) {
let disposed = false; let disposed = false;
const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true, powerPreference: "high-performance" }); const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true, powerPreference: "high-performance" });
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); renderer.setPixelRatio(Math.min(window.devicePixelRatio, 1.5));
renderer.toneMapping = THREE.ACESFilmicToneMapping; renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.toneMappingExposure = 1.08; renderer.toneMappingExposure = 1.08;
renderer.domElement.style.cssText = "width:100%;height:100%;display:block;cursor:grab;"; renderer.domElement.style.cssText = "width:100%;height:100%;display:block;cursor:grab;";
@@ -70,6 +72,18 @@ export function AntiquityViewer({ src }: { src: string }) {
}); });
controlsRef.current = controls; controlsRef.current = controls;
let needsRender = true;
let active = true;
const requestRender = () => {
needsRender = true;
};
controls.addEventListener("change", requestRender);
const stopGate = createRenderGate(mount, (isActive) => {
active = isActive;
if (isActive) requestRender();
});
const resize = () => { const resize = () => {
const w = mount.clientWidth; const w = mount.clientWidth;
const h = mount.clientHeight; const h = mount.clientHeight;
@@ -77,12 +91,14 @@ export function AntiquityViewer({ src }: { src: string }) {
renderer.setSize(w, h, false); renderer.setSize(w, h, false);
camera.aspect = w / h; camera.aspect = w / h;
camera.updateProjectionMatrix(); camera.updateProjectionMatrix();
needsRender = true;
}; };
const observer = new ResizeObserver(resize); const observer = new ResizeObserver(resize);
observer.observe(mount); observer.observe(mount);
resize(); resize();
const loader = new GLTFLoader(); const loader = new GLTFLoader();
loader.setMeshoptDecoder(MeshoptDecoder);
loader.load( loader.load(
src, src,
(gltf) => { (gltf) => {
@@ -108,6 +124,7 @@ export function AntiquityViewer({ src }: { src: string }) {
homeRef.current = { pos: camera.position.clone(), target: controls.target.clone() }; homeRef.current = { pos: camera.position.clone(), target: controls.target.clone() };
setStatus("ready"); setStatus("ready");
requestRender();
}, },
(event) => { (event) => {
if (event.total) setProgress(Math.round((event.loaded / event.total) * 100)); if (event.total) setProgress(Math.round((event.loaded / event.total) * 100));
@@ -118,9 +135,14 @@ export function AntiquityViewer({ src }: { src: string }) {
); );
const tick = () => { const tick = () => {
controls.update();
renderer.render(scene, camera);
raf = requestAnimationFrame(tick); raf = requestAnimationFrame(tick);
if (!active) return;
// controls.update() trả về true khi camera còn chuyển động (damping/autoRotate)
const moving = controls.update();
if (needsRender || moving || controls.autoRotate) {
renderer.render(scene, camera);
needsRender = false;
}
}; };
tick(); tick();
@@ -128,6 +150,8 @@ export function AntiquityViewer({ src }: { src: string }) {
disposed = true; disposed = true;
cancelAnimationFrame(raf); cancelAnimationFrame(raf);
observer.disconnect(); observer.disconnect();
stopGate();
controls.removeEventListener("change", requestRender);
controls.dispose(); controls.dispose();
scene.traverse((object) => { scene.traverse((object) => {
const mesh = object as THREE.Mesh; const mesh = object as THREE.Mesh;