Hi, I am playing with progressive shadowmap and I don’t fully understand how its performance behaves.
What I want to achieve is first update lightmap around 200 times, and then take a screenshot, I tried with renderer.domElement.toDataURL, renderer.readRenderTargetPixels and even with puppeteer and taking screenshot with it, in all these solutions performance works the same.
When I update lightmap 200 times with requestAnimationFrame, it takes around 10 seconds and after that when I call renderer.todataURL it works quickly, but when I execute lightmap update 200 times in a simple for loop and then I call renderer.toDataURL, for loop executes in a fraction of a second, but toDataURL takes 10 seconds to complete.
I also tried using requestAnimationFrame in puppeteer with disabled FPS cap (I assumed that could run faster since it runs with 3000FPS that way) but toDataURL executed also for around 10 seconds.
Also when I instead of calling toDataURL, I call renderer.render after for loop, this single render will take 10 seconds.
What is causing toDataURL/render after for loop to take that long to execute? Is there any way of optimizing this?
Here is my code:
import * as THREE from 'three';
import { ProgressiveLightMap } from 'three/addons/misc/ProgressiveLightMap.js';
import Stats from 'three/addons/libs/stats.module.js';
console.log(`three.js version ${THREE.REVISION}`);
const SHADOW_MAP_SIZE = 2048;
const ITERATION_COUNT = 200;
const LIGHT_COUNT = 20;
const USE_ASYNC = false;
function resize() {
renderer.setPixelRatio(window.devicePixelRatio);
const width = document.body.clientWidth;
const height = document.body.clientHeight;
renderer.setSize(width, height);
camera.aspect = width / height;
if (camera.isOrthographicCamera) {
const cameraHeight = 2;
camera.left = -cameraHeight / 2 * width / height;
camera.right = cameraHeight / 2 * width / height;
camera.top = cameraHeight / 2;
camera.bottom = -cameraHeight / 2;
}
camera.updateProjectionMatrix();
}
window.addEventListener('resize', resize);
//SETTING UP THREE.JS SCENE.
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 0.01, 1000);
const renderer = new THREE.WebGLRenderer({ alpha: true, antialias: true, preserveDrawingBuffer: true });
renderer.setSize(document.body.clientWidth, document.body.clientHeight);
renderer.shadowMap.enabled = true;
renderer.shadowMap.autoUpdate = false;
renderer.shadowMap.needsUpdate = true;
const stats = new Stats();
stats.showPanel(0);
document.body.appendChild(stats.dom);
camera.position.set(1, 1, 1);
camera.lookAt(new THREE.Vector3);
const floor = new THREE.Mesh(new THREE.PlaneGeometry(1, 1), new THREE.MeshLambertMaterial());
floor.receiveShadow = true;
floor.rotation.x = -Math.PI / 2;
floor.name = 'floor';
scene.add(floor);
const cube = new THREE.Mesh(new THREE.BoxGeometry(0.2, 0.2, 0.2), new THREE.MeshLambertMaterial());
cube.receiveShadow = true;
cube.castShadow = true;
cube.position.y = 0.1;
scene.add(cube);
cube.name = 'cube';
const directionalLights = new THREE.Group();
scene.add(directionalLights);
for (let i = 0; i < LIGHT_COUNT; i ++) {
const light = new THREE.DirectionalLight(0xffffff, 0.7 / 20);
light.position.x = Math.random() * 2 - 1;
light.position.y = Math.random() * 2;
light.position.z = Math.random() * 2 - 1; light.near = 0;
light.far = 10;
light.castShadow = true;
light.shadow.left = -0.5;
light.shadow.right = 0.5;
light.shadow.bottom = -0.5;
light.shadow.top = 0.5;
light.shadow.mapSize.width = light.shadow.mapSize.height = SHADOW_MAP_SIZE;
directionalLights.add(light);
}
const ambientLight = new THREE.AmbientLight(0xffffff, 0.2);
scene.add(ambientLight);
const lightMap = new ProgressiveLightMap(renderer);
lightMap.addObjectsToLightMap([floor, cube, ambientLight, ...directionalLights.children]);
let start;
function render() {
stats.begin();
lightMap.update(camera);
stats.end();
}
async function preRenderAsync() {
for (let i = 0; i < ITERATION_COUNT; i ++) {
await new Promise(resolve => {
requestAnimationFrame(() => {
render();
resolve();
});
});
}
}
function preRender() {
for (let i = 0; i < ITERATION_COUNT; i ++) {
render();
}
}
start = Date.now();
if (USE_ASYNC) await preRenderAsync();
else preRender();
console.log(`Pre render:\t\t\t${((Date.now() - start) / 1000).toFixed(2)}s`);
start = Date.now();
renderer.domElement.toDataURL();
console.log(`First toDataURL:\t${((Date.now() - start) / 1000).toFixed(2)}s`);
start = Date.now();
renderer.render(scene, camera);
console.log(`First render:\t\t${((Date.now() - start) / 1000).toFixed(2)}s`);
start = Date.now();
lightMap.update(camera);
console.log(`Lightmap update:\t${((Date.now() - start) / 1000).toFixed(2)}s`);
start = Date.now();
renderer.domElement.toDataURL();
console.log(`Second toDataURL:\t${((Date.now() - start) / 1000).toFixed(2)}s`);
start = Date.now();
renderer.render(scene, camera);
console.log(`Second render:\t\t${((Date.now() - start) / 1000).toFixed(2)}s`);
document.body.appendChild(renderer.domElement);
resize();
function animate() {
stats.begin();
requestAnimationFrame(animate);
renderer.render(scene, camera);
stats.end();
}
animate();