ประสิทธิภาพ - วิธีลดการใช้งาน CPU/GPU ของ three.js ในเบราว์เซอร์
ในตอนนี้ฉันมีลูกโลกเคลื่อนไหวที่หมุนไปมาและจุดบนลูกโลกเปลี่ยนสีแบบสุ่ม มันทำงานได้ดี แต่ถ้าปล่อยไว้ในพื้นหลัง มันจะทำให้แล็ปท็อปของฉันช้าลงมาก มีการเปลี่ยนแปลงใดที่ฉันสามารถทำได้เพื่อลดปริมาณหน่วยความจำที่มันใช้หรือไม่?
ในตัวจัดการงานบน Chrome ฉันเห็นว่ามันใช้ CPU 12% และหน่วยความจำ GPU 128MB เมื่อแท็บนั้นเปิดใช้งาน นี่ถือเป็นเรื่องปกติสำหรับ three.js หรือโค้ดจำเป็นต้องมีการเปลี่ยนแปลง?
ngAfterViewInit() {
if(this.enabled) {
this.controls = new OrbitControls(this.camera, this.renderer.domElement);
this.controls.rotateSpeed = 0.5;
this.controls.enableDamping = true;
this.controls.dampingFactor = 0.5;
this.controls.rotationSpeed = 0.3;
this.controls.enableZoom = false;
this.controls.autoRotate = true;
this.controls.autoRotateSpeed = -1;
this.renderer.setSize(window.innerWidth, window.innerHeight);
this.rendererContainer.nativeElement.appendChild(this.renderer.domElement);
this.animate();
const timerId = setInterval(() => this.updateColor(), 650);
}
}
private get enabled(): boolean {
if(this._enabled!==undefined) {
return this._enabled;
}
const canvas = document.createElement("canvas");
const gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
this._enabled = gl && gl instanceof WebGLRenderingContext;
return this._enabled;
}
private initGlobe(): void {
this.scene = new THREE.Scene();
this.camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 1, 1000);
this.camera.position.set(0, 5, 15);
this.camera.lookAt(this.scene.position);
this.renderer = new THREE.WebGLRenderer({
antialias: true
});
this.renderer.setClearColor('rgb(55, 44, 80)');
this.geom = new THREE.SphereBufferGeometry(6, 350, 90);
this.colors = [];
this.color = new THREE.Color();
this.colorList = ['rgb(123, 120, 194)'];
for (let i = 0; i < this.geom.attributes.position.count; i++) {
this.color.set(this.colorList[THREE.Math.randInt(0, this.colorList.length - 1)]);
this.color.toArray(this.colors, i * 3);
}
this.geom.addAttribute('color', new THREE.BufferAttribute(new Float32Array(this.colors), 3));
this.geom.addAttribute('colorRestore', new THREE.BufferAttribute(new Float32Array(this.colors), 3));
this.loader = new THREE.TextureLoader();
this.loader.setCrossOrigin('');
this.texture = this.loader.load('/assets/globe-dot.jpg');
this.texture.wrapS = THREE.RepeatWrapping;
this.texture.wrapT = THREE.RepeatWrapping;
this.texture.repeat.set(1, 1);
const oval = this.loader.load('/assets/circle.png');
this.points = new THREE.Points(this.geom, new THREE.ShaderMaterial({
vertexColors: THREE.VertexColors,
uniforms: {
visibility: {
value: this.texture
},
shift: {
value: 0
},
shape: {
value: oval
},
size: {
value: 0.4
},
scale: {
value: 300
}
},
vertexShader: `
uniform float scale;
uniform float size;
varying vec2 vUv;
varying vec3 vColor;
void main() {
vUv = uv;
vColor = color;
vec4 mvPosition = modelViewMatrix * vec4( position, 0.99 );
gl_PointSize = size * ( scale / length( mvPosition.xyz )) * (0.3 + sin(uv.y * 3.1415926) * 0.35 );
gl_Position = projectionMatrix * mvPosition;
}
// `,
fragmentShader: `
uniform sampler2D visibility;
uniform float shift;
uniform sampler2D shape;
varying vec2 vUv;
varying vec3 vColor;
void main() {
vec2 uv = vUv;
uv.x += shift;
vec4 v = texture2D(visibility, uv);
if (length(v.rgb) > 1.0) discard;
gl_FragColor = vec4( vColor, 0.9 );
vec4 shapeData = texture2D( shape, gl_PointCoord );
if (shapeData.a < 0.0625) discard;
gl_FragColor = gl_FragColor * shapeData;
}
`,
transparent: false
}));
this.points.sizeAttenuation = false;
this.scene.add(this.points);
this.globe = new THREE.Mesh(this.geom, new THREE.MeshBasicMaterial({
color: 'rgb(65, 54, 88)', transparent: true, opacity: 0.5
}));
this.globe.scale.setScalar(0.99);
this.points.add(this.globe);
this.scene.add(this.globe);
}
animate() {
this.controls.update();
this.renderer.render(this.scene, this.camera);
this.animationQueue.push(this.animate);
window.requestAnimationFrame(_ => this.nextAnimation());
}
nextAnimation() {
try {
const animation = this.animationQueue.shift();
if (animation instanceof Function) {
animation.bind(this)();
}
} catch (e) {
console.error(e);
}
}
updateColor() {
for (let i = 0; i < this.usedIndices.length; i++) {
let idx = this.usedIndices[i];
this.geom.attributes.color.copyAt(idx, this.geom.attributes.colorRestore, idx);
}
for (let i = 0; i < this.pointsUsed; i++) {
let idx = THREE.Math.randInt(0, this.geom.attributes.color.count - 1);
if (idx%5 == 0 && idx%1 == 0) {
this.geom.attributes.color.setXYZ(idx, 0.9, 0.3, 0);
}
else {
this.geom.attributes.color.setXYZ(idx, 1, 1, 1);
}
this.usedIndices[i] = idx;
}
this.geom.attributes.color.needsUpdate = true;
ฉันได้ดูคำถามอื่นๆ ที่แนะนำให้รวมเมชเข้าด้วยกัน แต่ฉันไม่แน่ใจว่านั่นจะใช้ได้ที่นี่ ขอบคุณ!
คำตอบ #1
มันขึ้นอยู่กับว่าคุณหมายถึง 'ภูมิหลัง' อะไร
ถ้าหมายถึง "พื้นหลัง" ว่า "ไม่ใช่แท็บด้านหน้า" ดังนั้น ถ้าคุณกำลังใช้ requestAnimationFrame (ซึ่งคุณกำลังใช้) ถ้าเพจของคุณไม่ใช่แท็บหน้าของเบราว์เซอร์หรือถ้าคุณย่อหน้าต่างเบราว์เซอร์ เบราว์เซอร์จะหยุดส่งเหตุการณ์ animation frame ให้คุณ และเพจของคุณควรหยุดทำงานโดยสมบูรณ์
ถ้าคุณหมายถึง “พื้นหลัง” เป็นแท็บหน้าแต่ของหน้าต่างที่ไม่ได้ย่อและก็ไม่ใช่หน้าต่างหน้าแรกด้วย คุณสามารถใช้เหตุการณ์ blur และ focus เพื่อหยุดเพจทั้งหมดได้
ตัวอย่าง: หมายเหตุ: เหตุการณ์ blur ดูเหมือนไม่ทำงานใน iframe ดังนั้นมันจะไม่ทำงานในตัวอย่างด้านล่าง แต่ถ้าคุณคัดลอกไปยังไฟล์ มันควรจะทำงาน
let requestId;
function start() {
if (!requestId) {
requestId = requestAnimationFrame(animate);
}
}
function stop() {
console.log('stop');
if (requestId) {
cancelAnimationFrame(requestId);
requestId = undefined;
}
}
const ctx = document.querySelector("canvas").getContext('2d');
function animate(time) {
requestId = undefined;
ctx.save();
ctx.translate(
150 + 150 * Math.cos(time * 0.001),
75 + 75 * Math.sin(time * 0.003),
);
ctx.scale(
Math.cos(time * 0.005),
Math.cos(time * 0.007),
);
ctx.fillStyle = `hsl(${time % 360},100%,50%)`;
ctx.fillRect(-50, 50, 100, 100);
ctx.restore();
start();
}
start();
window.addEventListener('blur', stop);
window.addEventListener('focus', start);body { margin: 0; }
canvas { width: 100vw; height: 100vh; display: block; }<canvas></canvas>แน่นอน แทนที่จะหยุดการทำงานทั้งหมดเมื่อเกิด blur คุณสามารถจำกัดความถี่ของแอปของคุณเองได้ เช่น เรนเดอร์ทุกเฟรมที่ 5 หรือเรนเดอร์สิ่งที่น้อยลง เป็นต้น...
